From d71d0c2435aa44eb1ef5e08b36497cd9fe9eb9be Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Tue, 27 Feb 2024 14:13:03 +0100 Subject: [PATCH 01/73] WIP - pair programming session w Elias --- sandbox/minib.nim | 45 ++++++++++++++++++++++++++++++++++ sandbox/minib2.nim | 12 +++++++++ sandbox/nim.cfg | 2 ++ sandbox/notes.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 sandbox/minib.nim create mode 100644 sandbox/minib2.nim create mode 100644 sandbox/nim.cfg create mode 100644 sandbox/notes.md diff --git a/sandbox/minib.nim b/sandbox/minib.nim new file mode 100644 index 0000000..18d6297 --- /dev/null +++ b/sandbox/minib.nim @@ -0,0 +1,45 @@ +# minib (a mini nimib) +import strformat, macros + +macro toStr*(body: untyped): string = + (body.toStrLit) + +type + NbBase* = ref object of RootObj + partial*: string + NbText* = ref object of NbBase + text*: string + NbCode* = ref object of NbBase + code*: string + output*: string + NbImage* = ref object of NbBase + url*: string + NbSummaryDetails* = ref object of NbBase + summary*: string + details*: seq[NbBase] + NbDoc* = object + blocks*: seq[NbBase] + +template nbInit = + var nb {. inject .}: NbDoc + +template nbImage(url2: string) = + nb.blocks.add NbImage( + url: url2, + partial: "" + ) + +# alternative api, similar to python, not used in nimib +proc image(nb: var NbDoc, url: string) = + nb.blocks.add NbImage(url: url) + +when isMainModule: + import print + + nbInit + nbImage: "img1" + nb.image("img2") + + print nb + print nb.blocks[0].NbImage.url + print nb.blocks[1].NbImage.url \ No newline at end of file diff --git a/sandbox/minib2.nim b/sandbox/minib2.nim new file mode 100644 index 0000000..10cca72 --- /dev/null +++ b/sandbox/minib2.nim @@ -0,0 +1,12 @@ +import json + +type + NbBlock* = ref object + command*: string + blocks*: seq[NbBlock] + data*: JsonNode + NbCode* = distinct NbBlock + +func code(b: NbCode): string = + assert "code" in b.NbBlock.data + assert b.NbBlock.data["code"].kind == JString diff --git a/sandbox/nim.cfg b/sandbox/nim.cfg new file mode 100644 index 0000000..0b88f6e --- /dev/null +++ b/sandbox/nim.cfg @@ -0,0 +1,2 @@ +--hints:off +--warnings:off \ No newline at end of file diff --git a/sandbox/notes.md b/sandbox/notes.md new file mode 100644 index 0000000..b162f03 --- /dev/null +++ b/sandbox/notes.md @@ -0,0 +1,61 @@ +annoying: +- just noticed that this PR broke locally my tests: https://github.com/pietroppeter/nimib/pull/211/files +- I am still using 1.6! not good! +- still do not have a way to switch easily + +plan for today: +- this is a summary of nimib and refactoring: https://gist.github.com/pietroppeter/0a3699531c8059eea9094e076ac15f9f +- discuss and see critical points +- if we manage to code stuff, better + +plan: +- let's start in a sandbox environment from scratch +- a new NbBlock type +- goals: + - support container blocks + - inheritance + - user can customize method (how? oop? or I just add a closure?) + - support json backend + - should work well with multi-language support + - might come from json backend + - improve locality of block definition +- current mechanism + - does not support container blocks + - blocks are defined in two places +- how to handle the customization of backend? + - willing to sacrifice this at the moment + - but ideally +- not in scope + - serialization and deserialization through jsony + - Hugo will take care of that + + + +discussion: +- **decision**: base block should be a containers block? + - yes: you mimic html where every node can have children nodes + - (argument for no) if yes all blocks inheriting would need to take into account the children blocks in the templating + - example of nbImage: argument for no +- idea: we could start with locality and start from api and work backwards + + +what kind of blocks do we have: +- a source block: where I need to capture the source code generating the block +- a capturing block: where stdout is captured +- a slim block: with no body +- a block with body: + - body can be executed + - body can be discarded + - (body can be altered) - no example +- a container block: that can contain other blocks inside +- blocks can have side effects: e.g. write to a file, read from a file +- needs rendering procs (postprocess), could be backend specific + +examples: +- nbCode: source, capturing, withBody(executed), needs postprocess (highlighting) +- nbText: slim block, needs postprocess (mdToHtml) +- nbImage: slim block +- nbSummaryDetails: container block + +a new minib: +- skip capturing \ No newline at end of file From 5b698ad1ebfb62cb32750e1b5f1117f0f6bf0e69 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Tue, 27 Feb 2024 17:47:13 +0100 Subject: [PATCH 02/73] review hugoop --- sandbox/hugoop/one.nim | 32 +++++++++++++++++++++++++ sandbox/hugoop/three.nim | 51 ++++++++++++++++++++++++++++++++++++++++ sandbox/hugoop/two.nim | 51 ++++++++++++++++++++++++++++++++++++++++ sandbox/notes.md | 39 ++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 sandbox/hugoop/one.nim create mode 100644 sandbox/hugoop/three.nim create mode 100644 sandbox/hugoop/two.nim diff --git a/sandbox/hugoop/one.nim b/sandbox/hugoop/one.nim new file mode 100644 index 0000000..2cac4ba --- /dev/null +++ b/sandbox/hugoop/one.nim @@ -0,0 +1,32 @@ +import jsony + +type + Nimib = ref object of RootObj + a, b, c: int + NimibChild = ref object of Nimib + d, e: float + + +method dump(n: Nimib): string = + n[].toJson() + +when not defined(noNimibChild): # essential, see below + method dump(n: NimibChild): string = + n[].toJson() + +proc dumpHook*(s: var string, v: Nimib) = + s.add v.dump() + +let n1: Nimib = Nimib(a: 1, b: 2, c: 3) +let n2: Nimib = NimibChild(d: 3.14, e: 4.56) + +echo n1.toJson() +echo n2.toJson() +#[ nim r one +{"a":1,"b":2,"c":3} +{"d":3.14,"e":4.56,"a":0,"b":0,"c":0} +]# +#[ nim -d:noNimibChild r one +{"a":1,"b":2,"c":3} +{"a":0,"b":0,"c":0} +]# \ No newline at end of file diff --git a/sandbox/hugoop/three.nim b/sandbox/hugoop/three.nim new file mode 100644 index 0000000..003fb55 --- /dev/null +++ b/sandbox/hugoop/three.nim @@ -0,0 +1,51 @@ +import jsony, tables + +type + Nimib = ref object of RootObj + a, b, c: int + typename: string + NimibChild = ref object of Nimib + d, e: float + +var parseDefs: Table[string, proc (s: string, i: var int): Nimib] + +template registerBlock(typename: untyped) = + parseDefs[$typename] = + proc (s: string, i: var int): Nimib = + var v: typename + new v + parseHook(s, i, v[]) + result = v + + method dump(n: typename): string = + n[].toJson() + +proc parseHook*(s: string, i: var int, v: var Nimib) = + # First parse the typename + var n: Nimib = Nimib() + let current_i = i + parseHook(s, i, n[]) + # Reset i + i = current_i + # Parse the correct type + let typename = n.typename + v = parseDefs[typename](s, i) + +registerBlock(Nimib) +registerBlock(NimibChild) + +# moved this here, I do not need to add dummy dumphook +proc dumpHook*(s: var string, v: Nimib) = + s.add v.dump() + + +let n1: Nimib = Nimib(a: 1, b: 2, c: 3, typename: "Nimib") +let n2: Nimib = NimibChild(a: 100, d: 3.14, e: 4.56, typename: "NimibChild") + +echo n1.toJson() +echo n2.toJson() + +let s1 = n1.toJson().fromJson(Nimib) +echo s1.toJson() +let s2 = n2.toJson().fromJson(Nimib) +echo s2.toJson() \ No newline at end of file diff --git a/sandbox/hugoop/two.nim b/sandbox/hugoop/two.nim new file mode 100644 index 0000000..a976dce --- /dev/null +++ b/sandbox/hugoop/two.nim @@ -0,0 +1,51 @@ +import jsony, tables + +type + Nimib = ref object of RootObj + a, b, c: int + typename: string + NimibChild = ref object of Nimib + d, e: float + +method dump(n: Nimib): string = + n[].toJson() + +method dump(n: NimibChild): string = + n[].toJson() + +proc dumpHook*(s: var string, v: Nimib) = + s.add v.dump() + +let n1: Nimib = Nimib(a: 1, b: 2, c: 3, typename: "Nimib") +let n2: Nimib = NimibChild(a: 100, d: 3.14, e: 4.56, typename: "NimibChild") + +echo n1.toJson() +echo n2.toJson() + +proc parseNimib(s: string, i: var int): Nimib = + var v = Nimib() + parseHook(s, i, v[]) + result = v + +proc parseNimibChild(s: string, i: var int): Nimib = + var v = NimibChild() + parseHook(s, i, v[]) + result = v + +let parseDefs = { + "Nimib": parseNimib, + "NimibChild": parseNimibChild +}.toTable() + +proc parseHook*(s: string, i: var int, v: var Nimib) = + var n: Nimib = Nimib() + let current_i = i + parseHook(s, i, n[]) + i = current_i + let typename = n.typename + v = parseDefs[typename](s, i) + +let s1 = n1.toJson().fromJson(Nimib) +echo s1.toJson() +let s2 = n2.toJson().fromJson(Nimib) +echo s2.toJson() \ No newline at end of file diff --git a/sandbox/notes.md b/sandbox/notes.md index b162f03..2deaaa4 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,3 +1,42 @@ +## Second day - Feb 28 + +- [x] review Hugo solution for oop +- define a scope and make explicit goals and assumptions +- start working on it, starting from api + +### scope + +- blocks: + - nbBlock + - nbCode, nbText, nbImage, nbSummaryDetails +- commands: + - nbInit, nbSave +- output on save: + - a basic html document with water.css +- support all 3 backends: html, json, md +- support 2 themes: default, debug (json in html) +- capture? maybe but no capture blocks inside other capture blocks +- try to minimize use of external templating system + - pluggable templating system? + + + + +## Pair programming session - Feb 27 + +GOALS of TODAY: pair program, get ideas and motivation. +Achieved. + +Main highlights: +- why I want to do OOP? need to be able to answer that +- start from API (in particular locality of definition of blocks) +- define a Scope of what I want to support +- is this an extension of a templating system? + +### details + +preparation + annoying: - just noticed that this PR broke locally my tests: https://github.com/pietroppeter/nimib/pull/211/files - I am still using 1.6! not good! From b663eb41b5ae533e84555033ed73489ae1b91431 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Tue, 27 Feb 2024 18:05:05 +0100 Subject: [PATCH 03/73] minib3: nbText, nbImage --- sandbox/minib3/minib.nim | 39 ++++++++++++++++++++++++++++++++++++++ sandbox/minib3/minilib.nim | 0 sandbox/notes.md | 9 +++++++++ 3 files changed, 48 insertions(+) create mode 100644 sandbox/minib3/minib.nim create mode 100644 sandbox/minib3/minilib.nim diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim new file mode 100644 index 0000000..661ca93 --- /dev/null +++ b/sandbox/minib3/minib.nim @@ -0,0 +1,39 @@ +# types.nim +type + NbBlock = ref object of RootObj + NbText = ref object of NbBlock + text: string + NbImage = ref object of NbBlock + url: string + NbDoc = object + blk: NbBlock + blocks: seq[NbBlock] + +# nimib.nim +template nbInit* = + var nb {. inject .}: NbDoc + +template nbText*(ttext: string) = + nb.blk = NbText(text: ttext) + nb.blocks.add nb.blk + +template nbImage*(turl: string) = + nb.blk = NbImage(url: turl) + nb.blocks.add nb.blk + +template nbSave* = + discard + +when isMainModule: + import print + # hello.nim + nbInit + nbText: "hi" + nbImage("img.png") + nbSave + + print nb + print nb.blocks[0] + print nb.blocks[0].NbText + # print nb.blocks[0].NbImage # correctly fails at runtime + print nb.blocks[1].NbImage diff --git a/sandbox/minib3/minilib.nim b/sandbox/minib3/minilib.nim new file mode 100644 index 0000000..e69de29 diff --git a/sandbox/notes.md b/sandbox/notes.md index 2deaaa4..febde85 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -2,6 +2,7 @@ - [x] review Hugo solution for oop - define a scope and make explicit goals and assumptions +- answer why OOP question - start working on it, starting from api ### scope @@ -19,8 +20,16 @@ - try to minimize use of external templating system - pluggable templating system? +### minib3 +implementation of above scope, structure: +- minib: main library and example +- minilib: non focus code that comes from existing nimib +todo: +- [x] structure +- [x] implement nbText, nbImage +- [ ] implement nbSave (html backend, default theme, echo to terminal) ## Pair programming session - Feb 27 From d2f243faa04acf06e687d6a60674812c95b731be Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Tue, 27 Feb 2024 21:40:45 +0100 Subject: [PATCH 04/73] minib3: refactor NbDoc as NbBlock and add Nb object --- sandbox/minib3/minib.nim | 21 +++++++++++++-------- sandbox/notes.md | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 661ca93..abb5778 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -1,25 +1,30 @@ # types.nim +import tables + type NbBlock = ref object of RootObj NbText = ref object of NbBlock text: string NbImage = ref object of NbBlock url: string - NbDoc = object - blk: NbBlock + NbDoc = ref object of NbBlock blocks: seq[NbBlock] + Nb = object + blk: NbBlock + doc: NbDoc # nimib.nim template nbInit* = - var nb {. inject .}: NbDoc + var nb {. inject .}: Nb + nb.doc = NbDoc() template nbText*(ttext: string) = nb.blk = NbText(text: ttext) - nb.blocks.add nb.blk + nb.doc.blocks.add nb.blk template nbImage*(turl: string) = nb.blk = NbImage(url: turl) - nb.blocks.add nb.blk + nb.doc.blocks.add nb.blk template nbSave* = discard @@ -33,7 +38,7 @@ when isMainModule: nbSave print nb - print nb.blocks[0] - print nb.blocks[0].NbText + print nb.doc.blocks[0] + print nb.doc.blocks[0].NbText # print nb.blocks[0].NbImage # correctly fails at runtime - print nb.blocks[1].NbImage + print nb.doc.blocks[1].NbImage diff --git a/sandbox/notes.md b/sandbox/notes.md index febde85..5672dd0 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -30,6 +30,12 @@ todo: - [x] structure - [x] implement nbText, nbImage - [ ] implement nbSave (html backend, default theme, echo to terminal) + - [x] refactor NbDoc as NbBlock and add Nb object + - [ ] NbRender + +#### key understanding + +- I think I want to make NbDoc a NbBlock and basically remove the mustache templating! ## Pair programming session - Feb 27 From d47349effab53e0111d6ce715270f7301ccb4fcd Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Tue, 27 Feb 2024 22:15:10 +0100 Subject: [PATCH 05/73] minib3: wip render (single backend) --- sandbox/minib3/minib.nim | 28 +++++++++++++++++++++++++++- sandbox/notes.md | 9 ++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index abb5778..1481ac0 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -3,29 +3,54 @@ import tables type NbBlock = ref object of RootObj + kind: string NbText = ref object of NbBlock text: string NbImage = ref object of NbBlock url: string NbDoc = ref object of NbBlock blocks: seq[NbBlock] + NbRenderFunc = proc (blk: NbBlock): string {. noSideEffect .} + NbRender = object + funcs: Table[string, NbRenderFunc] Nb = object - blk: NbBlock + blk: NbBlock # last block processed doc: NbDoc + backend: NbRender # nimib.nim template nbInit* = var nb {. inject .}: Nb nb.doc = NbDoc() + nb.doc.kind = "NbDoc" + nbInitBackend template nbText*(ttext: string) = nb.blk = NbText(text: ttext) + nb.blk.kind = "NbText" nb.doc.blocks.add nb.blk template nbImage*(turl: string) = nb.blk = NbImage(url: turl) + nb.blk.kind = "NbImage" nb.doc.blocks.add nb.blk +func nbImageToHtml*(blk: NbBlock): string = + let blk = blk.NbImage + "" + +template addToBackend*(kind: string, f: NbRenderFunc) = + nb.backend.funcs[kind] = f + +template nbInitBackend* = + addToBackend("NbImage", nbImageToHtml) + +func render(nb: Nb, blk: NbBlock): string = + if blk.kind in nb.backend.funcs: + nb.backend.funcs[blk.kind](blk) + else: + "" + template nbSave* = discard @@ -42,3 +67,4 @@ when isMainModule: print nb.doc.blocks[0].NbText # print nb.blocks[0].NbImage # correctly fails at runtime print nb.doc.blocks[1].NbImage + print nb.render nb.doc.blocks[1] \ No newline at end of file diff --git a/sandbox/notes.md b/sandbox/notes.md index 5672dd0..b55c110 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -31,11 +31,18 @@ todo: - [x] implement nbText, nbImage - [ ] implement nbSave (html backend, default theme, echo to terminal) - [x] refactor NbDoc as NbBlock and add Nb object - - [ ] NbRender + - [x] NbRender and NbRenderFunc + - [x] single backend (for now) + - [x] nb.render blk + - [x] nbImageToHtml + - [ ] nbTextToHtml + - [ ] nbDocToHtml + #### key understanding - I think I want to make NbDoc a NbBlock and basically remove the mustache templating! + (could still be reintroduced in a NbLegacyDoc for compatibility reasons) ## Pair programming session - Feb 27 From fd2dc273666758d424acf6a8984915a963094c91 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Wed, 28 Feb 2024 11:19:26 +0100 Subject: [PATCH 06/73] minib3: nbTextToHtml --- sandbox/minib3/minib.nim | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 1481ac0..95e1d9d 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -19,6 +19,8 @@ type backend: NbRender # nimib.nim +import markdown + template nbInit* = var nb {. inject .}: Nb nb.doc = NbDoc() @@ -39,11 +41,17 @@ func nbImageToHtml*(blk: NbBlock): string = let blk = blk.NbImage "" +func nbTextToHtml*(blk: NbBlock): string = + let blk = blk.NbText + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(blk.text, config=initGfmConfig()) + template addToBackend*(kind: string, f: NbRenderFunc) = nb.backend.funcs[kind] = f template nbInitBackend* = - addToBackend("NbImage", nbImageToHtml) + addToBackend("NbImage", nbImageToHtml) + addToBackend("NbText", nbTextToHtml) func render(nb: Nb, blk: NbBlock): string = if blk.kind in nb.backend.funcs: @@ -58,13 +66,14 @@ when isMainModule: import print # hello.nim nbInit - nbText: "hi" + nbText: "*hi*" nbImage("img.png") nbSave print nb print nb.doc.blocks[0] print nb.doc.blocks[0].NbText + print nb.render nb.doc.blocks[0] # print nb.blocks[0].NbImage # correctly fails at runtime print nb.doc.blocks[1].NbImage print nb.render nb.doc.blocks[1] \ No newline at end of file From 48fe2a394a9b621ce591ff7a093fabcba47e6ceb Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Wed, 28 Feb 2024 11:42:03 +0100 Subject: [PATCH 07/73] nbDocToHtml and completed nbSave --- sandbox/minib3/minib.nim | 27 +++++++++++++++++++++------ sandbox/notes.md | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 95e1d9d..76edde2 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -10,7 +10,7 @@ type url: string NbDoc = ref object of NbBlock blocks: seq[NbBlock] - NbRenderFunc = proc (blk: NbBlock): string {. noSideEffect .} + NbRenderFunc = proc (blk: NbBlock, nb: Nb): string {. noSideEffect .} NbRender = object funcs: Table[string, NbRenderFunc] Nb = object @@ -20,6 +20,7 @@ type # nimib.nim import markdown +import std / strutils template nbInit* = var nb {. inject .}: Nb @@ -37,30 +38,41 @@ template nbImage*(turl: string) = nb.blk.kind = "NbImage" nb.doc.blocks.add nb.blk -func nbImageToHtml*(blk: NbBlock): string = +func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "" -func nbTextToHtml*(blk: NbBlock): string = +func nbTextToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbText {.cast(noSideEffect).}: # not sure why markdown is marked with side effects markdown(blk.text, config=initGfmConfig()) +# forward declare +func render(nb: Nb, blk: NbBlock): string + +func nbDocToHtml*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbDoc + var blocks: seq[string] + for b in blk.blocks: + blocks.add nb.render(b) + "\n\n\n" & blocks.join("\n") & "\n\n" + template addToBackend*(kind: string, f: NbRenderFunc) = nb.backend.funcs[kind] = f template nbInitBackend* = addToBackend("NbImage", nbImageToHtml) addToBackend("NbText", nbTextToHtml) + addToBackend("NbDoc", nbDocToHtml) func render(nb: Nb, blk: NbBlock): string = if blk.kind in nb.backend.funcs: - nb.backend.funcs[blk.kind](blk) + nb.backend.funcs[blk.kind](blk, nb) else: "" template nbSave* = - discard + echo nb.render nb.doc when isMainModule: import print @@ -76,4 +88,7 @@ when isMainModule: print nb.render nb.doc.blocks[0] # print nb.blocks[0].NbImage # correctly fails at runtime print nb.doc.blocks[1].NbImage - print nb.render nb.doc.blocks[1] \ No newline at end of file + print nb.render nb.doc.blocks[1] + + print nb.render nb.doc + \ No newline at end of file diff --git a/sandbox/notes.md b/sandbox/notes.md index b55c110..ba51e65 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,7 +1,20 @@ +## Naming + +- not sure I still like the name backend for the rendering engine + that changes from blocks to string (and could be a themed html, a json, markdown...) + +### notes on backends + +- I could implement specific behaviours: + - markdown backend defaults to html backend when not defined + - html backend could default to a commented json output when not defined + - json backend should always be defined + - but stuff can be marked as to be skipped, e.g. the default theme should not be serialized + ## Second day - Feb 28 - [x] review Hugo solution for oop -- define a scope and make explicit goals and assumptions +- [x] define a scope and make explicit goals and assumptions - answer why OOP question - start working on it, starting from api @@ -29,14 +42,28 @@ implementation of above scope, structure: todo: - [x] structure - [x] implement nbText, nbImage -- [ ] implement nbSave (html backend, default theme, echo to terminal) +- [x] implement nbSave (html backend, default theme, echo to terminal) - [x] refactor NbDoc as NbBlock and add Nb object - [x] NbRender and NbRenderFunc - [x] single backend (for now) - [x] nb.render blk - [x] nbImageToHtml - - [ ] nbTextToHtml - - [ ] nbDocToHtml + - [x] nbTextToHtml + - [x] nbDocToHtml +- [ ] implement json backend +- later: + - md backend + - nbCode, nbSummaryDetails + - sugar for block creation + - ... + +decisions: +- 1) what do I pass to the render function? + - docs and any container block will need the full backend + - currently passing the whole Nb object but it might be an overkill +- 2) where is the default backend defined? + - currently in Nb object (same as before) + #### key understanding From 0932cdb7bf1bf85959db62f8c367bae686ba99be Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Thu, 29 Feb 2024 17:19:12 +0100 Subject: [PATCH 08/73] more understanding on how this should look (less progress on code) --- sandbox/minib3/minib.nim | 89 ++++++++++++++++++++++++---------------- sandbox/notes.md | 17 +++++++- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 76edde2..8842fa6 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -4,23 +4,36 @@ import tables type NbBlock = ref object of RootObj kind: string - NbText = ref object of NbBlock - text: string - NbImage = ref object of NbBlock - url: string - NbDoc = ref object of NbBlock - blocks: seq[NbBlock] NbRenderFunc = proc (blk: NbBlock, nb: Nb): string {. noSideEffect .} NbRender = object funcs: Table[string, NbRenderFunc] + NbDoc = ref object of NbBlock + blocks: seq[NbBlock] Nb = object blk: NbBlock # last block processed - doc: NbDoc + doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc backend: NbRender +# this needs to be know for all container blocks +func render(nb: Nb, blk: NbBlock): string = + if blk.kind in nb.backend.funcs: + nb.backend.funcs[blk.kind](blk, nb) + else: + "" + +# themes.nim +import std / strutils + +func nbDocToHtml*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbDoc + var blocks: seq[string] + for b in blk.blocks: + blocks.add nb.render(b) + "\n\n\n" & blocks.join("\n") & "\n\n" + + # nimib.nim import markdown -import std / strutils template nbInit* = var nb {. inject .}: Nb @@ -28,34 +41,48 @@ template nbInit* = nb.doc.kind = "NbDoc" nbInitBackend +template nbSave* = + echo nb.render nb.doc + +# all other blocks are in a sense all custom blocks +# we could add sugar for common block creation +type + NbText = ref object of NbBlock + text: string template nbText*(ttext: string) = - nb.blk = NbText(text: ttext) - nb.blk.kind = "NbText" + nb.blk = NbText(text: ttext, kind: "NbText") nb.doc.blocks.add nb.blk +func nbTextToHtml*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbText + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(blk.text, config=initGfmConfig()) +#[ the above could be shortened with sugar to: +newNbBlock(nbText): + text: string + toHtml: + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(blk.text, config=initGfmConfig()) +]# + + +type + NbImage = ref object of NbBlock + url: string template nbImage*(turl: string) = - nb.blk = NbImage(url: turl) - nb.blk.kind = "NbImage" + nb.blk = NbImage(url: turl, kind: "NbImage") nb.doc.blocks.add nb.blk - func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "" +#[ the above could be shortened with sugar to: +newNbBlock(nbImage): + url: string + toHtml: + "" +]# -func nbTextToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbText - {.cast(noSideEffect).}: # not sure why markdown is marked with side effects - markdown(blk.text, config=initGfmConfig()) - -# forward declare -func render(nb: Nb, blk: NbBlock): string -func nbDocToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbDoc - var blocks: seq[string] - for b in blk.blocks: - blocks.add nb.render(b) - "\n\n\n" & blocks.join("\n") & "\n\n" template addToBackend*(kind: string, f: NbRenderFunc) = nb.backend.funcs[kind] = f @@ -65,15 +92,6 @@ template nbInitBackend* = addToBackend("NbText", nbTextToHtml) addToBackend("NbDoc", nbDocToHtml) -func render(nb: Nb, blk: NbBlock): string = - if blk.kind in nb.backend.funcs: - nb.backend.funcs[blk.kind](blk, nb) - else: - "" - -template nbSave* = - echo nb.render nb.doc - when isMainModule: import print # hello.nim @@ -91,4 +109,3 @@ when isMainModule: print nb.render nb.doc.blocks[1] print nb.render nb.doc - \ No newline at end of file diff --git a/sandbox/notes.md b/sandbox/notes.md index ba51e65..e6e895b 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -11,6 +11,22 @@ - json backend should always be defined - but stuff can be marked as to be skipped, e.g. the default theme should not be serialized +## Third day - Feb 29 + +- goal: add json backend +- useful distraction: added a potential idea for sugar for creating blocks +- interesting question: now NbDoc is special, what if I make it non special? +- an idea I am realizing: + - all side effects should happen during block creation + - block rendering should be kept pure + - in particular this means that block that need an id from global nb object need to generate it at block creation time! +- reminder for later: to fully support custom container blocks I will need to add in + Nb object something that tells me where to add next block + +todo: +- [x] restructured minib and added sugar for blocks +- [ ] json backend + ## Second day - Feb 28 - [x] review Hugo solution for oop @@ -65,7 +81,6 @@ decisions: - currently in Nb object (same as before) - #### key understanding - I think I want to make NbDoc a NbBlock and basically remove the mustache templating! From 2ad8153eee39cd929640fdb215fb3c53c0d20580 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Thu, 29 Feb 2024 18:19:05 +0100 Subject: [PATCH 09/73] json backend now works! --- sandbox/minib3/minib.nim | 95 +++++++++++++++++++++++++++----------- sandbox/minib3/minilib.nim | 0 sandbox/notes.md | 3 +- 3 files changed, 70 insertions(+), 28 deletions(-) delete mode 100644 sandbox/minib3/minilib.nim diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 8842fa6..e821a0f 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -14,13 +14,49 @@ type doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc backend: NbRender -# this needs to be know for all container blocks +# this needs to be know for all container blocks not sure whether to put it in types func render(nb: Nb, blk: NbBlock): string = if blk.kind in nb.backend.funcs: nb.backend.funcs[blk.kind](blk, nb) else: "" +# globals.nim +var nbToJson: Table[string, proc (s: string, i: var int): NbBlock] +var nbToHtml: NbRender # since we need it for json, let's make it also for html + +# jsons.nim +import jsony + +template addNbBlockToJson*(kind: untyped) = + nbToJson[$kind] = + proc (s: string, i: var int): NbBlock = + var v: kind + new v + parseHook(s, i, v[]) + result = v + + method dump(n: kind): string = + n[].toJson() + +proc parseHook*(s: string, i: var int, v: var NbBlock) = + # First parse the typename + var n: NbBlock = NbBlock() + let current_i = i + parseHook(s, i, n[]) + # Reset i + i = current_i + # Parse the correct type + let kind = n.kind + v = nbToJson[kind](s, i) + +method dump(n: NbBlock): string = + n[].toJson() + +proc dumpHook*(s: var string, v: NbBlock) = + s.add v.dump() + + # themes.nim import std / strutils @@ -30,16 +66,16 @@ func nbDocToHtml*(blk: NbBlock, nb: Nb): string = for b in blk.blocks: blocks.add nb.render(b) "\n\n\n" & blocks.join("\n") & "\n\n" - +addNbBlockToJson(NbDoc) # nimib.nim import markdown template nbInit* = var nb {. inject .}: Nb - nb.doc = NbDoc() - nb.doc.kind = "NbDoc" - nbInitBackend + nb.doc = NbDoc(kind: "NbDoc") + nbToHtml.funcs["NbDoc"] = nbDocToHtml + nb.backend = nbToHtml template nbSave* = echo nb.render nb.doc @@ -56,6 +92,8 @@ func nbTextToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbText {.cast(noSideEffect).}: # not sure why markdown is marked with side effects markdown(blk.text, config=initGfmConfig()) +nbToHtml.funcs["NbText"] = nbTextToHtml +addNbBlockToJson(NbText) #[ the above could be shortened with sugar to: newNbBlock(nbText): text: string @@ -65,7 +103,6 @@ newNbBlock(nbText): ]# - type NbImage = ref object of NbBlock url: string @@ -75,6 +112,8 @@ template nbImage*(turl: string) = func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "" +nbToHtml.funcs["NbImage"] = nbImageToHtml +addNbBlockToJson(NbImage) #[ the above could be shortened with sugar to: newNbBlock(nbImage): url: string @@ -82,30 +121,32 @@ newNbBlock(nbImage): "" ]# - - -template addToBackend*(kind: string, f: NbRenderFunc) = - nb.backend.funcs[kind] = f - -template nbInitBackend* = - addToBackend("NbImage", nbImageToHtml) - addToBackend("NbText", nbTextToHtml) - addToBackend("NbDoc", nbDocToHtml) - when isMainModule: import print - # hello.nim nbInit nbText: "*hi*" nbImage("img.png") nbSave - - print nb - print nb.doc.blocks[0] - print nb.doc.blocks[0].NbText - print nb.render nb.doc.blocks[0] - # print nb.blocks[0].NbImage # correctly fails at runtime - print nb.doc.blocks[1].NbImage - print nb.render nb.doc.blocks[1] - - print nb.render nb.doc + #[ + + + +

hi

+ + + + + ]# + + let docToJson = nb.doc.toJson() + let docFromJson = docToJson.fromJson(NbDoc) + print docToJson + print docFromJson + print docFromJson.blocks[0].NbText + print docFromJson.blocks[1].NbImage + #[ + docToJson="{"blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"}],"kind":"NbDoc"}" + docFromJson=NbDoc:ObjectType(blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage")], kind: "NbDoc") + docFromJson.blocks[0].NbText=NbText:ObjectType(text: "*hi*", kind: "NbText") + docFromJson.blocks[1].NbImage=NbImage:ObjectType(url: "img.png", kind: "NbImage") + ]# diff --git a/sandbox/minib3/minilib.nim b/sandbox/minib3/minilib.nim deleted file mode 100644 index e69de29..0000000 diff --git a/sandbox/notes.md b/sandbox/notes.md index e6e895b..e9aca67 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -22,10 +22,11 @@ - in particular this means that block that need an id from global nb object need to generate it at block creation time! - reminder for later: to fully support custom container blocks I will need to add in Nb object something that tells me where to add next block +- removed minilib, not useful since I would need import and that's a different problem todo: - [x] restructured minib and added sugar for blocks -- [ ] json backend +- [x] json backend (hugo's work is great!) ## Second day - Feb 28 From a52dbe66c8440578732f98ab2b2b88d2791de6fd Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Sat, 2 Mar 2024 10:35:16 +0100 Subject: [PATCH 10/73] notes from fourth day --- sandbox/notes.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sandbox/notes.md b/sandbox/notes.md index e9aca67..627d719 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -11,6 +11,19 @@ - json backend should always be defined - but stuff can be marked as to be skipped, e.g. the default theme should not be serialized +## Notes from Fourth day - Match 1st + +Some notes for me for later while I am on mobile related to NbRender type: +- right now NbDoc forced me to change the signature to add also Nb object +- I think I should change to pass only NbDoc object (type of the root object) +- also I could have NbRender support two types of functions (one for NbDoc like blocks one for NbText like blocks) +- I should encode the block type in the base block +- the general idea is to make the internal api safer (allow only what needs to be allowed) +- also the idea is to have the NbDoc the unit of serialization and rendering should not access anything outside of that (it will not be accessible from a deserialized version) +- and in NbDoc the plan is to have a theme field that is skipped on json serialization +- there is a tension between what you want to be able to serialize (content and specificities of a page) and what you want to be controlled (later) by the SSG (like the theme) and should not be serialized (also because it is redundant). Tension also with a third element which is the fact that rendering does need a theme +- as another, unrelated note, minimal theme could contain only doctype html and title element (see https://unplannedobsolescence.com/blog/best-hello-world-web-development/) + ## Third day - Feb 29 - goal: add json backend @@ -87,7 +100,7 @@ decisions: - I think I want to make NbDoc a NbBlock and basically remove the mustache templating! (could still be reintroduced in a NbLegacyDoc for compatibility reasons) -## Pair programming session - Feb 27 +## Pair programming session - Feb 26 GOALS of TODAY: pair program, get ideas and motivation. Achieved. From ffb4bd98ad5b06db6d816ec6b54c244916c94580 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Sat, 2 Mar 2024 10:46:38 +0100 Subject: [PATCH 11/73] notes from fifth day --- sandbox/notes.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/sandbox/notes.md b/sandbox/notes.md index 627d719..b6c471d 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -11,6 +11,35 @@ - json backend should always be defined - but stuff can be marked as to be skipped, e.g. the default theme should not be serialized +## Fifth day - Martch 2nd + +what could I work on: +- on theme + - use the minimal theme + - use a theme field that is skipped dring json generation +- on container blocks + - implement summary details + - implement a flexbox container! + - that and a generic styled span container could allow me to easily create a grid... +- for later + - md backend (and revision of backends) + - nbCode + - nbJs stuff + +more thoughts: +- can I also use a super method or something to wrap the rendered block in a NbBlock? + - for example it could be used to add a class name inside a div + - this could be important for example to customize code block appearance + - or to add an optional id to a block + - (this could anyway be added later) + - and it could be added as something that by default a block does during rendering + - it might have a different signature (takes rendering of content and outputs new rendering) +- restriction/variation of render method should be done towards the end, + so that I know what multiple blocks should be able to +- instead of passing Nb object to render method I could pass a pointer to the current render backend + - one could use this to do fun stuff, for example have a container block that chnages the rendering engine, I could show the output of json backend inside a html backend as raw json and so on + - oooh coool! + ## Notes from Fourth day - Match 1st Some notes for me for later while I am on mobile related to NbRender type: @@ -80,7 +109,7 @@ todo: - [x] nbImageToHtml - [x] nbTextToHtml - [x] nbDocToHtml -- [ ] implement json backend +- [x] implement json backend - later: - md backend - nbCode, nbSummaryDetails From 5e6727e0b9cca78795e3bfe1b2865dd1115fc38a Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Sat, 2 Mar 2024 10:55:56 +0100 Subject: [PATCH 12/73] [skip ci] fix type and test skip ci message --- sandbox/notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandbox/notes.md b/sandbox/notes.md index b6c471d..2acae11 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -11,7 +11,7 @@ - json backend should always be defined - but stuff can be marked as to be skipped, e.g. the default theme should not be serialized -## Fifth day - Martch 2nd +## Fifth day - March 2nd what could I work on: - on theme From c35114e462e20d13518b5688e3bc83b5d56ead0b Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Mon, 25 Mar 2024 13:48:26 +0100 Subject: [PATCH 13/73] add NbContainer --- sandbox/minib3/minib.nim | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index e821a0f..9f5d878 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -6,13 +6,17 @@ type kind: string NbRenderFunc = proc (blk: NbBlock, nb: Nb): string {. noSideEffect .} NbRender = object - funcs: Table[string, NbRenderFunc] - NbDoc = ref object of NbBlock + funcs: Table[string, NbRenderFunc] + NbContainer = ref object of NbBlock blocks: seq[NbBlock] + parent: NbContainer + NbDoc = ref object of NbContainer + title: string Nb = object blk: NbBlock # last block processed doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc - backend: NbRender + container: NbContainer # current container + backend: NbRender # current backend # this needs to be know for all container blocks not sure whether to put it in types func render(nb: Nb, blk: NbBlock): string = @@ -74,6 +78,7 @@ import markdown template nbInit* = var nb {. inject .}: Nb nb.doc = NbDoc(kind: "NbDoc") + nb.container = nb.doc nbToHtml.funcs["NbDoc"] = nbDocToHtml nb.backend = nbToHtml @@ -87,7 +92,7 @@ type text: string template nbText*(ttext: string) = nb.blk = NbText(text: ttext, kind: "NbText") - nb.doc.blocks.add nb.blk + nb.container.blocks.add nb.blk func nbTextToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbText {.cast(noSideEffect).}: # not sure why markdown is marked with side effects @@ -108,7 +113,7 @@ type url: string template nbImage*(turl: string) = nb.blk = NbImage(url: turl, kind: "NbImage") - nb.doc.blocks.add nb.blk + nb.container.blocks.add nb.blk func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "" From 954ef22413b005fdca26e652033847e87488819e Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo Date: Mon, 25 Mar 2024 14:00:54 +0100 Subject: [PATCH 14/73] change minimal theme and add NbContainerToHtml --- sandbox/minib3/minib.nim | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 9f5d878..4b60241 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -64,12 +64,17 @@ proc dumpHook*(s: var string, v: NbBlock) = # themes.nim import std / strutils -func nbDocToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbDoc - var blocks: seq[string] +func nbContainerToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbContainer for b in blk.blocks: - blocks.add nb.render(b) - "\n\n\n" & blocks.join("\n") & "\n\n" + result.add nb.render(b).strip & '\n' + result.strip + +func nbDocToHtml*(blk: NbBlock, nb: Nb): string = + "\n" & + "" & blk.NbDoc.title & "<title>\n" & + nbContainerToHtml(blk, nb) + addNbBlockToJson(NbDoc) # nimib.nim @@ -77,7 +82,7 @@ import markdown template nbInit* = var nb {. inject .}: Nb - nb.doc = NbDoc(kind: "NbDoc") + nb.doc = NbDoc(kind: "NbDoc", title: "a nimib document") nb.container = nb.doc nbToHtml.funcs["NbDoc"] = nbDocToHtml nb.backend = nbToHtml @@ -107,7 +112,6 @@ newNbBlock(nbText): markdown(blk.text, config=initGfmConfig()) ]# - type NbImage = ref object of NbBlock url: string @@ -126,6 +130,26 @@ newNbBlock(nbImage): "<img src= '" & blk.url & "'>" ]# +type + NbDetails = ref object of NbContainer + summary: string +template nbDetails*(tsummary: string, body: untyped) = + let blk = NbDetails(summary: tsummary, kind: "NbDetails") + blk.parent = nb.container # save current container + nb.blk = blk + nb.container.blocks.add nb.blk + + nb.container = blk + body + nb.container = blk.parent + +func NbDetailsToHtml*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbDetails + +nbToHtml.funcs["NbDetails"] = NbDetailsToHtml +addNbBlockToJson(NbDetails) + + when isMainModule: import print nbInit From 5d995ac6df4946de244110dcd5d7975b31e7537f Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo <pietro.peterlongo@gmail.com> Date: Wed, 27 Mar 2024 11:55:35 +0100 Subject: [PATCH 15/73] even a specific dumpHoook does not work! --- sandbox/minib3/minib.nim | 135 +++++++++++++++++++++++++++++++++++++-- sandbox/notes.md | 6 ++ 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index 4b60241..e00a640 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -54,12 +54,89 @@ proc parseHook*(s: string, i: var int, v: var NbBlock) = let kind = n.kind v = nbToJson[kind](s, i) +# I think these two skipHooks do NOT work since they refer +# to a ref object while jsony will dump the nonref object (for which I have no name!) +proc skipHook*(T: typedesc[NbContainer], key: static string): bool = + key in ["parent"] + +proc skipHook*(T: typedesc[NbDoc], key: static string): bool = + key in ["parent"] + method dump(n: NbBlock): string = n[].toJson() proc dumpHook*(s: var string, v: NbBlock) = s.add v.dump() +template dumpKey(s: var string, v: string) = + const v2 = v.toJson() & ":" + s.add v2 + + +proc dumpHook*(s: var string, v: NbContainer) = + s.add '{' + var i = 0 + when compiles(for k, e in v.pairs: discard): + # Tables and table like objects. + for k, e in v.pairs: + if i > 0: + s.add ',' + s.dumpHook(k) + s.add ':' + s.dumpHook(e) + inc i + else: + # Normal objects. + for k, e in v[].fieldPairs: + when compiles(skipHook(type(v), k)): + when skipHook(type(v), k): + discard + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + s.add '}' + +proc dumpHook*(s: var string, v: NbDoc) = + s.add '{' + var i = 0 + when compiles(for k, e in v.pairs: discard): + # Tables and table like objects. + for k, e in v.pairs: + if i > 0: + s.add ',' + s.dumpHook(k) + s.add ':' + s.dumpHook(e) + inc i + else: + # Normal objects. + for k, e in v[].fieldPairs: + when compiles(skipHook(type(v), k)): + when skipHook(type(v), k): + discard + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + s.add '}' + # themes.nim import std / strutils @@ -69,6 +146,7 @@ func nbContainerToHtml(blk: NbBlock, nb: Nb): string = for b in blk.blocks: result.add nb.render(b).strip & '\n' result.strip +# should I add this to the global object? func nbDocToHtml*(blk: NbBlock, nb: Nb): string = "<!DOCTYPE html>\n" & @@ -145,16 +223,51 @@ template nbDetails*(tsummary: string, body: untyped) = func NbDetailsToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbDetails + "<details><summary>" & blk.summary & "</summary>\n" & + nbContainerToHtml(blk, nb) & + "\n</details>" nbToHtml.funcs["NbDetails"] = NbDetailsToHtml addNbBlockToJson(NbDetails) +proc dumpHook*(s: var string, v: NbDetails) = + s.add '{' + var i = 0 + when compiles(for k, e in v.pairs: discard): + # Tables and table like objects. + for k, e in v.pairs: + if i > 0: + s.add ',' + s.dumpHook(k) + s.add ':' + s.dumpHook(e) + inc i + else: + # Normal objects. + for k, e in v[].fieldPairs: + when compiles(skipHook(type(v), k)): + when skipHook(type(v), k): + discard + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + else: + if i > 0: + s.add ',' + s.dumpKey(k) + s.dumpHook(e) + inc i + s.add '}' when isMainModule: import print nbInit - nbText: "*hi*" - nbImage("img.png") + nbDetails("Click for details:"): + nbText: "*hi*" + nbImage("img.png") nbSave #[ <!DOCTYPE html> @@ -167,12 +280,22 @@ when isMainModule: </html> ]# - let docToJson = nb.doc.toJson() - let docFromJson = docToJson.fromJson(NbDoc) + let docToJson = nb.doc.blocks[0].NbDetails.blocks.toJson() + print nb.doc.blocks[0].NbDetails + echo docToJson + print nb.doc.blocks[0].NbDetails.toJson() # fails because of too much recursion + # does not fail anymore if I add the specific dumpHook + let docFromJson = docToJson.fromJson(seq[NbBlock]) # but now this fails + print docFromJson + #[ print docToJson print docFromJson - print docFromJson.blocks[0].NbText - print docFromJson.blocks[1].NbImage + print docFromJson.blocks[0].NbDetails + print docFromJson.blocks[0].NbDetails.blocks[0].NbText + print docFromJson.blocks[0].NbDetails.blocks[1].NbImage + + ]# + #[ docToJson="{"blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"}],"kind":"NbDoc"}" docFromJson=NbDoc:ObjectType(blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage")], kind: "NbDoc") diff --git a/sandbox/notes.md b/sandbox/notes.md index 2acae11..95da46a 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,3 +1,9 @@ +## getting back working on this + +- added NbContainer type, refactored NbDoc added NbDetails as example +- FAILURE: serialization to json has issues! + +--- ## Naming - not sure I still like the name backend for the rendering engine From 5d761c324c13a82c46a0f60ad33233d56193ce2a Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo <pietro.peterlongo@gmail.com> Date: Wed, 27 Mar 2024 16:57:23 +0100 Subject: [PATCH 16/73] new NbContainer implementation, add api `nb.add` and `nb.withContainer(container) : body` --- sandbox/minib3/minib.nim | 115 +++++++++------------------------------ sandbox/notes.md | 6 +- 2 files changed, 30 insertions(+), 91 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index e00a640..c634912 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -9,13 +9,12 @@ type funcs: Table[string, NbRenderFunc] NbContainer = ref object of NbBlock blocks: seq[NbBlock] - parent: NbContainer NbDoc = ref object of NbContainer title: string Nb = object blk: NbBlock # last block processed doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc - container: NbContainer # current container + containers: seq[NbContainer] # current container backend: NbRender # current backend # this needs to be know for all container blocks not sure whether to put it in types @@ -54,14 +53,6 @@ proc parseHook*(s: string, i: var int, v: var NbBlock) = let kind = n.kind v = nbToJson[kind](s, i) -# I think these two skipHooks do NOT work since they refer -# to a ref object while jsony will dump the nonref object (for which I have no name!) -proc skipHook*(T: typedesc[NbContainer], key: static string): bool = - key in ["parent"] - -proc skipHook*(T: typedesc[NbDoc], key: static string): bool = - key in ["parent"] - method dump(n: NbBlock): string = n[].toJson() @@ -73,71 +64,6 @@ template dumpKey(s: var string, v: string) = s.add v2 -proc dumpHook*(s: var string, v: NbContainer) = - s.add '{' - var i = 0 - when compiles(for k, e in v.pairs: discard): - # Tables and table like objects. - for k, e in v.pairs: - if i > 0: - s.add ',' - s.dumpHook(k) - s.add ':' - s.dumpHook(e) - inc i - else: - # Normal objects. - for k, e in v[].fieldPairs: - when compiles(skipHook(type(v), k)): - when skipHook(type(v), k): - discard - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - s.add '}' - -proc dumpHook*(s: var string, v: NbDoc) = - s.add '{' - var i = 0 - when compiles(for k, e in v.pairs: discard): - # Tables and table like objects. - for k, e in v.pairs: - if i > 0: - s.add ',' - s.dumpHook(k) - s.add ':' - s.dumpHook(e) - inc i - else: - # Normal objects. - for k, e in v[].fieldPairs: - when compiles(skipHook(type(v), k)): - when skipHook(type(v), k): - discard - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - s.add '}' - - # themes.nim import std / strutils @@ -155,13 +81,26 @@ func nbDocToHtml*(blk: NbBlock, nb: Nb): string = addNbBlockToJson(NbDoc) +# blocks.nim +# should I add nb.blk.add blk to both add and with Container +proc add(nb: var Nb, blk: NbBlock) = + if nb.containers.len == 0: + nb.doc.blocks.add blk + else: + nb.containers[^1].blocks.add blk + +template withContainer(nb: var Nb, container: NbContainer, body: untyped) = + nb.add container + nb.containers.add container + body + discard nb.containers.pop + # nimib.nim import markdown template nbInit* = var nb {. inject .}: Nb nb.doc = NbDoc(kind: "NbDoc", title: "a nimib document") - nb.container = nb.doc nbToHtml.funcs["NbDoc"] = nbDocToHtml nb.backend = nbToHtml @@ -175,7 +114,7 @@ type text: string template nbText*(ttext: string) = nb.blk = NbText(text: ttext, kind: "NbText") - nb.container.blocks.add nb.blk + nb.add nb.blk func nbTextToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbText {.cast(noSideEffect).}: # not sure why markdown is marked with side effects @@ -195,7 +134,7 @@ type url: string template nbImage*(turl: string) = nb.blk = NbImage(url: turl, kind: "NbImage") - nb.container.blocks.add nb.blk + nb.add nb.blk func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "<img src= '" & blk.url & "'>" @@ -213,13 +152,9 @@ type summary: string template nbDetails*(tsummary: string, body: untyped) = let blk = NbDetails(summary: tsummary, kind: "NbDetails") - blk.parent = nb.container # save current container nb.blk = blk - nb.container.blocks.add nb.blk - - nb.container = blk - body - nb.container = blk.parent + nb.withContainer(blk): + body func NbDetailsToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbDetails @@ -280,13 +215,13 @@ when isMainModule: </html> ]# - let docToJson = nb.doc.blocks[0].NbDetails.blocks.toJson() - print nb.doc.blocks[0].NbDetails - echo docToJson - print nb.doc.blocks[0].NbDetails.toJson() # fails because of too much recursion - # does not fail anymore if I add the specific dumpHook - let docFromJson = docToJson.fromJson(seq[NbBlock]) # but now this fails + let docToJson = nb.doc.toJson() + print docToJson + let docFromJson = docToJson.fromJson(NbDoc) # but now this fails print docFromJson + print docFromJson.blocks[0].NbDetails + print docFromJson.blocks[0].NbDetails.blocks[0].NbText + print docFromJson.blocks[0].NbDetails.blocks[1].NbImage #[ print docToJson print docFromJson diff --git a/sandbox/notes.md b/sandbox/notes.md index 95da46a..f22f5c0 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,8 +1,12 @@ ## getting back working on this +### containers - added NbContainer type, refactored NbDoc added NbDetails as example - FAILURE: serialization to json has issues! - +- alternative implementation of containers that does not use circular references + (simpler, no issues) + - add api a `nb.add` and `nb.withContainer(container) +: body` --- ## Naming From 80cd2c50a2c35b386f68278a67efae5ebd5afd76 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo <pietro.peterlongo@gmail.com> Date: Wed, 27 Mar 2024 17:03:31 +0100 Subject: [PATCH 17/73] hide nb.blk api in add --- sandbox/minib3/minib.nim | 44 +++++++--------------------------------- sandbox/notes.md | 3 ++- 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/sandbox/minib3/minib.nim b/sandbox/minib3/minib.nim index c634912..166fde8 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib3/minib.nim @@ -82,8 +82,8 @@ func nbDocToHtml*(blk: NbBlock, nb: Nb): string = addNbBlockToJson(NbDoc) # blocks.nim -# should I add nb.blk.add blk to both add and with Container proc add(nb: var Nb, blk: NbBlock) = + nb.blk = blk if nb.containers.len == 0: nb.doc.blocks.add blk else: @@ -113,8 +113,8 @@ type NbText = ref object of NbBlock text: string template nbText*(ttext: string) = - nb.blk = NbText(text: ttext, kind: "NbText") - nb.add nb.blk + let blk = NbText(text: ttext, kind: "NbText") + nb.add blk func nbTextToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbText {.cast(noSideEffect).}: # not sure why markdown is marked with side effects @@ -133,8 +133,8 @@ type NbImage = ref object of NbBlock url: string template nbImage*(turl: string) = - nb.blk = NbImage(url: turl, kind: "NbImage") - nb.add nb.blk + let blk = NbImage(url: turl, kind: "NbImage") + nb.add blk func nbImageToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbImage "<img src= '" & blk.url & "'>" @@ -152,7 +152,6 @@ type summary: string template nbDetails*(tsummary: string, body: untyped) = let blk = NbDetails(summary: tsummary, kind: "NbDetails") - nb.blk = blk nb.withContainer(blk): body @@ -164,37 +163,6 @@ func NbDetailsToHtml*(blk: NbBlock, nb: Nb): string = nbToHtml.funcs["NbDetails"] = NbDetailsToHtml addNbBlockToJson(NbDetails) -proc dumpHook*(s: var string, v: NbDetails) = - s.add '{' - var i = 0 - when compiles(for k, e in v.pairs: discard): - # Tables and table like objects. - for k, e in v.pairs: - if i > 0: - s.add ',' - s.dumpHook(k) - s.add ':' - s.dumpHook(e) - inc i - else: - # Normal objects. - for k, e in v[].fieldPairs: - when compiles(skipHook(type(v), k)): - when skipHook(type(v), k): - discard - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - else: - if i > 0: - s.add ',' - s.dumpKey(k) - s.dumpHook(e) - inc i - s.add '}' when isMainModule: @@ -203,6 +171,8 @@ when isMainModule: nbDetails("Click for details:"): nbText: "*hi*" nbImage("img.png") + nbDetails("go deeper"): + nbText("42") nbSave #[ <!DOCTYPE html> diff --git a/sandbox/notes.md b/sandbox/notes.md index f22f5c0..d78cbd0 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -4,9 +4,10 @@ - added NbContainer type, refactored NbDoc added NbDetails as example - FAILURE: serialization to json has issues! - alternative implementation of containers that does not use circular references - (simpler, no issues) + (simpler, no issues) [x] - add api a `nb.add` and `nb.withContainer(container) : body` +- hidden nb.blk api in nb.add (we might remove it later) [x] --- ## Naming From e338f67f23b250bcd436e2e1e3e4d530583a6826 Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo <pietro.peterlongo@gmail.com> Date: Wed, 27 Mar 2024 17:07:48 +0100 Subject: [PATCH 18/73] update dev notes --- sandbox/notes.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sandbox/notes.md b/sandbox/notes.md index d78cbd0..b2d87e9 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,6 +1,14 @@ ## getting back working on this -### containers +### Next steps: +- implement `nbCode` in minib +- implement `nbCodeFromJs` in minib +- think if there are essentially other types of blocks we need to test with the new refactoring +- if not go ahead and implement sugar to implement custom blocks (and refactor) +- clean up minib implementation +- reimplement all of minib starting with the new minib poc + +### containers 👌 - added NbContainer type, refactored NbDoc added NbDetails as example - FAILURE: serialization to json has issues! - alternative implementation of containers that does not use circular references @@ -8,7 +16,9 @@ - add api a `nb.add` and `nb.withContainer(container) : body` - hidden nb.blk api in nb.add (we might remove it later) [x] + --- + ## Naming - not sure I still like the name backend for the rendering engine From ad3b14847ea156f36898fd749e96acc6c8852c9a Mon Sep 17 00:00:00 2001 From: Pietro Peterlongo <pietro.peterlongo@gmail.com> Date: Thu, 28 Mar 2024 10:07:14 +0100 Subject: [PATCH 19/73] implement nbCode --- sandbox/minib.nim | 45 ---------------- sandbox/minib/capture.nim | 30 +++++++++++ sandbox/{minib3 => minib}/minib.nim | 83 ++++++++++++++++++++++------- sandbox/minib2.nim | 12 ----- sandbox/notes.md | 4 +- 5 files changed, 97 insertions(+), 77 deletions(-) delete mode 100644 sandbox/minib.nim create mode 100644 sandbox/minib/capture.nim rename sandbox/{minib3 => minib}/minib.nim (68%) delete mode 100644 sandbox/minib2.nim diff --git a/sandbox/minib.nim b/sandbox/minib.nim deleted file mode 100644 index 18d6297..0000000 --- a/sandbox/minib.nim +++ /dev/null @@ -1,45 +0,0 @@ -# minib (a mini nimib) -import strformat, macros - -macro toStr*(body: untyped): string = - (body.toStrLit) - -type - NbBase* = ref object of RootObj - partial*: string - NbText* = ref object of NbBase - text*: string - NbCode* = ref object of NbBase - code*: string - output*: string - NbImage* = ref object of NbBase - url*: string - NbSummaryDetails* = ref object of NbBase - summary*: string - details*: seq[NbBase] - NbDoc* = object - blocks*: seq[NbBase] - -template nbInit = - var nb {. inject .}: NbDoc - -template nbImage(url2: string) = - nb.blocks.add NbImage( - url: url2, - partial: "<img href=\"{{url}}\"><img>" - ) - -# alternative api, similar to python, not used in nimib -proc image(nb: var NbDoc, url: string) = - nb.blocks.add NbImage(url: url) - -when isMainModule: - import print - - nbInit - nbImage: "img1" - nb.image("img2") - - print nb - print nb.blocks[0].NbImage.url - print nb.blocks[1].NbImage.url \ No newline at end of file diff --git a/sandbox/minib/capture.nim b/sandbox/minib/capture.nim new file mode 100644 index 0000000..c3884f2 --- /dev/null +++ b/sandbox/minib/capture.nim @@ -0,0 +1,30 @@ +## This submodule implements only one template to temporarily redirect and capture stdout during execution of a chunk of code. +import tempfile + +# see https://stackoverflow.com/questions/64026829/how-to-temporarily-capture-stdout +# Thanks, Clonk! + +# low level, should be Posix only but they happen to work on (my) Windows, too! +proc dup(oldfd: FileHandle): FileHandle {.importc, header: "unistd.h".} +proc dup2(oldfd: FileHandle, newfd: FileHandle): cint {.importc, header: "unistd.h".} + +template captureStdout*(ident: untyped, body: untyped) = + ## redirect stdout to a temporary file and captures output of body in ident + let + # Duplicate stdout + stdoutFileno = stdout.getFileHandle() + stdoutDupfd = dup(stdoutFileno) + # Create a new temporary file + (tmpFile, tmpFilename) = mkstemp(mode=fmWrite) + tmpFileFd: FileHandle = tmpFile.getFileHandle() + discard dup2(tmpFileFd, stdoutFileno) # writing to stdoutFileno now writes to tmpFile + + body + flushFile stdout + + # before reading tmpFile, flush and close + tmpFile.flushFile() + tmpFile.close() + ident = readFile(tmpFileName) + # Restore stdout + discard dup2(stdoutDupfd, stdoutFileno) \ No newline at end of file diff --git a/sandbox/minib3/minib.nim b/sandbox/minib/minib.nim similarity index 68% rename from sandbox/minib3/minib.nim rename to sandbox/minib/minib.nim index 166fde8..f1ec2aa 100644 --- a/sandbox/minib3/minib.nim +++ b/sandbox/minib/minib.nim @@ -51,6 +51,8 @@ proc parseHook*(s: string, i: var int, v: var NbBlock) = i = current_i # Parse the correct type let kind = n.kind + if kind notIn nbToJson: + raise ValueError.newException "cannot find kind in nbToJson: \"" & kind & '"' v = nbToJson[kind](s, i) method dump(n: NbBlock): string = @@ -82,6 +84,8 @@ func nbDocToHtml*(blk: NbBlock, nb: Nb): string = addNbBlockToJson(NbDoc) # blocks.nim +import std / macros + proc add(nb: var Nb, blk: NbBlock) = nb.blk = blk if nb.containers.len == 0: @@ -95,6 +99,12 @@ template withContainer(nb: var Nb, container: NbContainer, body: untyped) = body discard nb.containers.pop +macro toStr*(body: untyped): string = + (body.toStrLit) + +# capture.nim +import capture # imported from old nimib (new nimib does not run locally for me!) + # nimib.nim import markdown @@ -155,15 +165,36 @@ template nbDetails*(tsummary: string, body: untyped) = nb.withContainer(blk): body -func NbDetailsToHtml*(blk: NbBlock, nb: Nb): string = +func nbDetailsToHtml*(blk: NbBlock, nb: Nb): string = let blk = blk.NbDetails "<details><summary>" & blk.summary & "</summary>\n" & nbContainerToHtml(blk, nb) & "\n</details>" -nbToHtml.funcs["NbDetails"] = NbDetailsToHtml +nbToHtml.funcs["NbDetails"] = nbDetailsToHtml addNbBlockToJson(NbDetails) +type + NbCode = ref object of NbBlock + code: string + output: string + lang: string +func nbCodeToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbCode + "<pre><code class=\"" & blk.lang & "\">\n" & + blk.code & '\n' & + "</code></pre>\n" & + "<pre>\n" & + blk.output & '\n' & + "</pre>" +nbToHtml.funcs["NbCode"] = nbCodeToHtml +addNbBlockToJson(NbCode) +template nbCode*(body: untyped) = + let blk = NbCode(lang: "nim", kind: "NbCode") + nb.add blk + blk.code = toStr(body) + captureStdout(blk.output): + body when isMainModule: import print @@ -173,16 +204,27 @@ when isMainModule: nbImage("img.png") nbDetails("go deeper"): nbText("42") + nbCode: + echo "hi" nbSave #[ <!DOCTYPE html> -<html><head></head> -<body> +<title>a nimib document<title> +<details><summary>Click for details:</summary> <p><em>hi</em></p> - <img src= 'img.png'> -</body> -</html> +<details><summary>go deeper</summary> +<p>42</p> +</details> +</details> +<pre><code class="nim"> + +echo "hi" +</code></pre> +<pre> +hi + +</pre> ]# let docToJson = nb.doc.toJson() @@ -192,18 +234,21 @@ when isMainModule: print docFromJson.blocks[0].NbDetails print docFromJson.blocks[0].NbDetails.blocks[0].NbText print docFromJson.blocks[0].NbDetails.blocks[1].NbImage + print docFromJson.blocks[1].NbCode #[ - print docToJson - print docFromJson - print docFromJson.blocks[0].NbDetails - print docFromJson.blocks[0].NbDetails.blocks[0].NbText - print docFromJson.blocks[0].NbDetails.blocks[1].NbImage - + docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" +docFromJson=NbDoc:ObjectType( + title: "a nimib document", + blocks: @[NbBlock:ObjectType(kind: "NbDetails"), NbBlock:ObjectType(kind: "NbCode")], + kind: "NbDoc" +) +docFromJson.blocks[0].NbDetails=NbDetails:ObjectType( + summary: "Click for details:", + blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage"), NbBlock:ObjectType(kind: "NbDetails")], + kind: "NbDetails" +) +docFromJson.blocks[0].NbDetails.blocks[0].NbText=NbText:ObjectType(text: "*hi*", kind: "NbText") +docFromJson.blocks[0].NbDetails.blocks[1].NbImage=NbImage:ObjectType(url: "img.png", kind: "NbImage") +docFromJson.blocks[1].NbCode=NbCode:ObjectType(code: "\necho "hi"", output: "hi\n", lang: "nim", kind: "NbCode") ]# - #[ - docToJson="{"blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"}],"kind":"NbDoc"}" - docFromJson=NbDoc:ObjectType(blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage")], kind: "NbDoc") - docFromJson.blocks[0].NbText=NbText:ObjectType(text: "*hi*", kind: "NbText") - docFromJson.blocks[1].NbImage=NbImage:ObjectType(url: "img.png", kind: "NbImage") - ]# diff --git a/sandbox/minib2.nim b/sandbox/minib2.nim deleted file mode 100644 index 10cca72..0000000 --- a/sandbox/minib2.nim +++ /dev/null @@ -1,12 +0,0 @@ -import json - -type - NbBlock* = ref object - command*: string - blocks*: seq[NbBlock] - data*: JsonNode - NbCode* = distinct NbBlock - -func code(b: NbCode): string = - assert "code" in b.NbBlock.data - assert b.NbBlock.data["code"].kind == JString diff --git a/sandbox/notes.md b/sandbox/notes.md index b2d87e9..d76a6a2 100644 --- a/sandbox/notes.md +++ b/sandbox/notes.md @@ -1,11 +1,13 @@ ## getting back working on this ### Next steps: -- implement `nbCode` in minib +- implement `nbCode` in minib [x] + - add (old!) implementation of captureStdout to minib / capture [x] - implement `nbCodeFromJs` in minib - think if there are essentially other types of blocks we need to test with the new refactoring - if not go ahead and implement sugar to implement custom blocks (and refactor) - clean up minib implementation + - remove previous minibs and rename minib3 to minib [x] - reimplement all of minib starting with the new minib poc ### containers 👌 From 53c17fe9dddc735503041877d9026bad40550b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sat, 18 May 2024 15:10:55 +0200 Subject: [PATCH 20/73] copy jsutils to minib --- sandbox/minib/jsutils.nim | 228 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 sandbox/minib/jsutils.nim diff --git a/sandbox/minib/jsutils.nim b/sandbox/minib/jsutils.nim new file mode 100644 index 0000000..de17e93 --- /dev/null +++ b/sandbox/minib/jsutils.nim @@ -0,0 +1,228 @@ +import std / [macros, macrocache, tables, strutils, strformat, sequtils, sugar, os, hashes] + +proc contains(tab: CacheTable, keyToCheck: string): bool = + for key, val in tab: + if key == keyToCheck: + return true + return false + +const bodyCache = CacheTable"bodyCache" + +macro addBody(key: string, s: untyped): untyped = + if key.strVal notin bodyCache: + bodyCache[key.strVal] = s + +proc degensymAst(n: NimNode) = + for i in 0 ..< n.len: + case n[i].kind + of nnkIdent, nnkSym: + let str = n[i].strVal + if "`gensym" in str: + let newStr = str.split("`gensym")[0] + let newSym = ident(newStr) + n[i] = newSym + else: + degensymAst(n[i]) + +proc genCapturedAssignment(capturedVariables, capturedTypes: seq[NimNode]): tuple[code: NimNode, placeholders: seq[NimNode]] = + result.code = newStmtList() + # generate fromJSON loading and then add entire body afterwards + if capturedVariables.len > 0: + for (cap, capType) in zip(capturedVariables, capturedTypes): + let placeholder = gensym(ident="placeholder") + + let newSym = cap + result.placeholders.add placeholder + result.code.add quote do: + let `newSym` = parseJson(`placeholder`).to(`capType`) # we must gensym `cap` as well! + +macro nimToJsStringSecondStage*(key: static string, putCodeInBlock: static bool, captureVars: varargs[typed]): untyped = + let captureVars = toSeq(captureVars) + + let captureTypes = collect: + for cap in captureVars: + cap.getTypeInst + + # Get the untyped body from CacheTable + var body: NimNode + if key in bodyCache: + body = bodyCache[key] + else: + error(&"Nimib error: key {key} not in any of the tables. Please open an issue on Github with a minimal reproducible example") + # Now we have the body! + body = body.copyNimTree() + # 1. Generate the captured variable assignments and return placeholders + let (capAssignments, placeholders) = genCapturedAssignment(captureVars, captureTypes) + # 2. Stringify code + var code = newStmtList(capAssignments, body).copyNimTree() + code.degensymAst() + + if putCodeInBlock: + code = newBlockStmt(code) + var codeText = code.toStrLit + # 3. Generate code which does the serialization and replacement of placeholders + let codeTextIdent = genSym(NimSymKind.nskVar, ident="codeText") + result = newStmtList() + result.add newVarStmt(codeTextIdent, codeText) + for i in 0 .. captureVars.high: + let placeholder = placeholders[i].repr.newLit + let varIdent = captureVars[i] + let serializedValue = quote do: + $(toJson(`varIdent`)) + result.add quote do: + `codeTextIdent` = `codeTextIdent`.replace(`placeholder`, "\"\"\"" & `serializedValue` & "\"\"\"") + # return tuple of the transformed code to be compiled and the prettified code for visualization + body.degensymAst() # remove `gensym if code was written in a template + result.add nnkTupleConstr.newTree(codeTextIdent, body.toStrLit) + +macro nimToJsString*(putCodeInBlock: static bool, args: varargs[untyped]): untyped = + if args.len == 0: + error("nbJsFromCode needs a code block to be passed", args) + + let body = args[^1] + let captureVars = + if args.len == 1: + @[] + else: + args[0 ..< ^1] + + # Save UNTYPED body for access later. + # It's important that it is untyped to be able to combine + # multiple code snippets. + let key = body.repr + + result = newStmtList() + result.add quote do: + addBody(`key`, `body`) + var nextArgs = @[newLit(key), newLit(putCodeInBlock)] + nextArgs.add captureVars + result.add newCall("nimToJsStringSecondStage", nextArgs) + +macro nbKaraxCodeBackend*(rootId: untyped, args: varargs[untyped]) = + if args.len == 0: + error("nbKaraxCode needs a code block to be passed", args) + + let body = args[^1] + let captureVars = + if args.len == 1: + @[] + else: + args[0 ..< ^1] + + let newBody = quote do: + import karax / [kbase, karax, karaxdsl, vdom, compact, jstrutils, kdom] + + var postRenderCallback: proc () = nil + + template postRender(body: untyped) = + ## Must be called before karaxHtml!!! + proc tempProc () = + body + postRenderCallback = tempProc + + template karaxHtml(body: untyped) = + proc createDom(): VNode = + result = buildHtml(tdiv): + body # html karax code + setRenderer(createDom, root=`rootId`.cstring, clientPostRenderCallback = postRenderCallback) + + `body` + + var callArgs = @[rootId] + callArgs.add captureVars + callArgs.add newBody + + let call = newCall(ident"nbJsFromCodeOwnFile", callArgs) + + result = call + +macro nbHappyxCodeBackend*(rootId: untyped, args: varargs[untyped]) = + if args.len == 0: + error("nbHappyxCode needs a code block to be passed", args) + let body = args[^1] + let captureVars = + if args.len == 1: + @[] + else: + args[0 ..< ^1] + + let caller = body[^1][0] + if caller != ident"happyxRoutes": + error(fmt "{caller} != happyxRoutes: a happyxRoutes: block must terminate the nbHappyxCode block") + + let happyxRoutesBody = body[^1][1] + + let preHappyxRoutes = + if body.len == 1: + @[] + else: + body[0 ..< ^1] + + var preRoutesCode = newStmtList() + for i in preHappyxRoutes.items: + preRoutesCode.add(i) + + let imports = quote do: + import happyx, std/ strformat + + let newBody = quote do: + `imports` + `preRoutesCode` + appRoutes(`rootId`): + `happyxRoutesBody` + + var callArgs = @[rootId] + callArgs.add captureVars + callArgs.add newBody + + let call = newCall(ident"nbJsFromCodeOwnFile", callArgs) + result = call + +#[ proc compileNimToJs*(doc: var NbDoc, blk: var NbBlock) = + let tempdir = getTempDir() / "nimib" + createDir(tempdir) + let (dir, filename, ext) = doc.thisFile.splitFile() + let nimfile = dir / (filename & "_nbCodeToJs_" & $doc.newId() & ext).RelativeFile + let jsfile = tempdir / &"out{hash(doc.thisFile)}.js" + var codeText = blk.context["transformedCode"].vString + let nbJsCounter = doc.nbJsCounter + doc.nbJsCounter += 1 + var bumpGensymString = """ +import std/[macros, json] + +macro bumpGensym(n: static int) = + for i in 0 .. n: + let _ = gensym() + +""" + bumpGensymString.add &"bumpGensym({nbJsCounter})\n" + codeText = bumpGensymString & codeText + writeFile(nimfile, codeText) + let kxiname = "nimib_kxi_" & $doc.newId() + let errorCode = execShellCmd(&"nim js -d:danger -d:kxiname=\"{kxiname}\" -o:{jsfile} {nimfile}") + if errorCode != 0: + raise newException(OSError, "The compilation of a javascript file failed! Did you remember to capture all needed variables?\n" & $nimfile) + removeFile(nimfile) + let jscode = readFile(jsfile) + removeFile(jsfile) + blk.output = jscode + blk.context["output"] = jscode + +proc nbCollectAllNbJs*(doc: var NbDoc) = + var topCode = "" # placed at the top (nbJsFromCodeGlobal) + var code = "" + for blk in doc.blocks: + if blk.command == "nbJsFromCode": + if blk.context["putAtTop"].vBool: + topCode.add "\n" & blk.context["transformedCode"].vString + else: + code.add "\n" & blk.context["transformedCode"].vString + code = topCode & "\n" & code + + if not code.isEmptyOrWhitespace: + # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) + var blk = NbBlock(command: "nbJsFromCodeOwnFile", code: code, context: newContext(searchDirs = @[], partials = doc.partials), output: "") + blk.context["transformedCode"] = code + doc.blocks.add blk + doc.blk = blk + ]# \ No newline at end of file From b8333bad142773f3a02fde0214eba7d489d2437a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sat, 18 May 2024 15:11:30 +0200 Subject: [PATCH 21/73] start implementing nbJsFromCode --- sandbox/minib/minib.nim | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index f1ec2aa..66aed42 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -107,6 +107,7 @@ import capture # imported from old nimib (new nimib does not run locally for me! # nimib.nim import markdown +import jsutils template nbInit* = var nb {. inject .}: Nb @@ -196,6 +197,23 @@ template nbCode*(body: untyped) = captureStdout(blk.output): body +type + NbJsFromCode = ref object of NbBlock + code: string + transformedCode: string + putAtTop: bool +func nbJsFromCodeToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbJsFromCode + blk.code +nbToHtml.funcs["NbJsFromCode"] = nbJsFromCodeToHtml +addNbBlockToJson(NbJsFromCode) +template nbJsFromCode*(args: varargs[untyped]) = + let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = NbJsFromCode(code: originalCode, transformedCode: transformedCode, putAtTop: false, kind: "NbJsFromCode") + nb.add blk + + + when isMainModule: import print nbInit @@ -206,6 +224,8 @@ when isMainModule: nbText("42") nbCode: echo "hi" + nbJsFromCode: + echo "bye" nbSave #[ <!DOCTYPE html> @@ -235,6 +255,7 @@ hi print docFromJson.blocks[0].NbDetails.blocks[0].NbText print docFromJson.blocks[0].NbDetails.blocks[1].NbImage print docFromJson.blocks[1].NbCode + print docFromJson.blocks[2].NbJsFromCode #[ docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" docFromJson=NbDoc:ObjectType( From 70dd4c8470633a7d769cca13428d2bd042b8ae92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sat, 18 May 2024 15:27:09 +0200 Subject: [PATCH 22/73] add showCode parameter to NbJsFromCode --- sandbox/minib/minib.nim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 66aed42..2b4a166 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -202,14 +202,18 @@ type code: string transformedCode: string putAtTop: bool + showCode: bool func nbJsFromCodeToHtml(blk: NbBlock, nb: Nb): string = let blk = blk.NbJsFromCode - blk.code + if blk.showCode: + "<pre><code class=\"nim\">\n" & blk.code & "\n</code></pre>" + else: + "" nbToHtml.funcs["NbJsFromCode"] = nbJsFromCodeToHtml addNbBlockToJson(NbJsFromCode) template nbJsFromCode*(args: varargs[untyped]) = let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) - let blk = NbJsFromCode(code: originalCode, transformedCode: transformedCode, putAtTop: false, kind: "NbJsFromCode") + let blk = NbJsFromCode(code: originalCode, transformedCode: transformedCode, putAtTop: false, showCode: false, kind: "NbJsFromCode") nb.add blk From 45d2ca62222528122de8d9989b24f7d7ef2fa1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sat, 18 May 2024 21:03:43 +0200 Subject: [PATCH 23/73] hacky nbJsFromCode implementation --- sandbox/minib/minib.nim | 94 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 2b4a166..68239dd 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -1,6 +1,7 @@ # types.nim -import tables - +import std/[tables, os, strformat, hashes] +import "$nim/compiler/pathutils" +import print type NbBlock = ref object of RootObj kind: string @@ -11,6 +12,8 @@ type blocks: seq[NbBlock] NbDoc = ref object of NbContainer title: string + id: int + nbJsCounter: int Nb = object blk: NbBlock # last block processed doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc @@ -24,6 +27,11 @@ func render(nb: Nb, blk: NbBlock): string = else: "" +proc newId*(doc: var NbDoc): int = + ## Provides a unique integer each time it is called + result = doc.id + inc doc.id + # globals.nim var nbToJson: Table[string, proc (s: string, i: var int): NbBlock] var nbToHtml: NbRender # since we need it for json, let's make it also for html @@ -78,7 +86,7 @@ func nbContainerToHtml(blk: NbBlock, nb: Nb): string = func nbDocToHtml*(blk: NbBlock, nb: Nb): string = "<!DOCTYPE html>\n" & - "<title>" & blk.NbDoc.title & "<title>\n" & + "<title>" & blk.NbDoc.title & "\n" & nbContainerToHtml(blk, nb) addNbBlockToJson(NbDoc) @@ -116,7 +124,10 @@ template nbInit* = nb.backend = nbToHtml template nbSave* = + echo "saving" + nbCollectAllNbJs(nb) echo nb.render nb.doc + writeFile("test.html", nb.render(nb.doc)) # all other blocks are in a sense all custom blocks # we could add sugar for common block creation @@ -216,6 +227,82 @@ template nbJsFromCode*(args: varargs[untyped]) = let blk = NbJsFromCode(code: originalCode, transformedCode: transformedCode, putAtTop: false, showCode: false, kind: "NbJsFromCode") nb.add blk +type + NbJsFromCodeOwnFile = ref object of NbBlock + code: string + transformedCode: string + jsCode: string + showCode: bool +func nbJsFromCodeOwnFileToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbJsFromCodeOwnFile + result = + if blk.showCode: + "
\n" & blk.code & "\n
\n" + else: + "" + result &= &"" +nbToHtml.funcs["NbJsFromCodeOwnFile"] = nbJsFromCodeOwnFileToHtml +addNbBlockToJson(NbJsFromCodeOwnFile) +template nbJsFromCodeOwnFile*(args: varargs[untyped]) = + let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = NbJsFromCodeOwnFile(code: originalCode, transformedCode: transformedCode, showCode: false, kind: "NbJsFromCodeOwnFile") + nb.add blk + +# jsutils.nim (these currently require to know of NbJsFromCode and NbJsFromCodeOwnFile...) +proc compileNimToJs*(nb: var Nb, blk: NbBlock): string = + let blk = blk.NbJsFromCodeOwnFile + let tempdir = getTempDir() / "nimib" + createDir(tempdir) + let (dir, filename, ext) = (getCurrentDir().AbsoluteDir, "js_file", ".nim")#doc.thisFile.splitFile() + let nimfile = dir / (filename & "_nbCodeToJs_" & $nb.doc.newId() & ext).RelativeFile + let jsfile = tempdir / &"out{hash(nb.doc.title)}.js" + var codeText = blk.transformedCode + let nbJsCounter = nb.doc.nbJsCounter + nb.doc.nbJsCounter += 1 + var bumpGensymString = """ +import std/[macros, json] + +macro bumpGensym(n: static int) = + for i in 0 .. n: + let _ = gensym() + +""" + bumpGensymString.add &"bumpGensym({nbJsCounter})\n" + codeText = bumpGensymString & codeText + writeFile(nimfile, codeText) + let kxiname = "nimib_kxi_" & $nb.doc.newId() + let errorCode = execShellCmd(&"nim js -d:danger -d:kxiname=\"{kxiname}\" -o:{jsfile} {nimfile}") + if errorCode != 0: + raise newException(OSError, "The compilation of a javascript file failed! Did you remember to capture all needed variables?\n" & $nimfile) + removeFile(nimfile) + let jscode = readFile(jsfile) + removeFile(jsfile) + return jscode + +proc nbCollectAllNbJs*(nb: var Nb) = + echo "In code!" + var topCode = "" # placed at the top (nbJsFromCodeGlobal) + var code = "" + for blk in nb.doc.blocks: # this won't work for containers, implement nb.flattenBlocks func + if blk.kind == "NbJsFromCode": + let blk = blk.NbJsFromCode + if blk.putAtTop: + topCode.add "\n" & blk.transformedCode + else: + code.add "\n" & blk.transformedCode + code = topCode & "\n" & code + + print code + if not code.isEmptyOrWhitespace: + # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) + let blk = NbJsFromCodeOwnFile(kind: "NbJsFromCodeOwnFile", code: "", transformedCode: code, showCode: false) + nb.add blk + + # loop over all nbJsFromCodeOwnFile and compile them + for blk in nb.doc.blocks: + if blk.kind == "NbJsFromCodeOwnFile": + var blk = blk.NbJsFromCodeOwnFile + blk.jsCode = nb.compileNimToJs(blk) when isMainModule: @@ -260,6 +347,7 @@ hi print docFromJson.blocks[0].NbDetails.blocks[1].NbImage print docFromJson.blocks[1].NbCode print docFromJson.blocks[2].NbJsFromCode + print docFromJson.blocks[3].NbJsFromCodeOwnFile #[ docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" docFromJson=NbDoc:ObjectType( From 73efd5161e0c2208cb9c6df90fb2966ccd8c3361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sun, 19 May 2024 10:53:28 +0200 Subject: [PATCH 24/73] use of syntex instead of checking kind --- sandbox/minib/minib.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 68239dd..ea0ab36 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -284,7 +284,7 @@ proc nbCollectAllNbJs*(nb: var Nb) = var topCode = "" # placed at the top (nbJsFromCodeGlobal) var code = "" for blk in nb.doc.blocks: # this won't work for containers, implement nb.flattenBlocks func - if blk.kind == "NbJsFromCode": + if blk of NbJsFromCode: let blk = blk.NbJsFromCode if blk.putAtTop: topCode.add "\n" & blk.transformedCode @@ -300,7 +300,7 @@ proc nbCollectAllNbJs*(nb: var Nb) = # loop over all nbJsFromCodeOwnFile and compile them for blk in nb.doc.blocks: - if blk.kind == "NbJsFromCodeOwnFile": + if blk of NbJsFromCodeOwnFile: var blk = blk.NbJsFromCodeOwnFile blk.jsCode = nb.compileNimToJs(blk) @@ -316,7 +316,7 @@ when isMainModule: nbCode: echo "hi" nbJsFromCode: - echo "bye" + echo "bye!" nbSave #[ From 08b5eeaa0c3b3c061579a4b8a920f6b78f2b13af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sun, 19 May 2024 12:36:03 +0200 Subject: [PATCH 25/73] implement blocksFlattened func for container blocks and use it --- sandbox/minib/minib.nim | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index ea0ab36..7fc8448 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -32,6 +32,12 @@ proc newId*(doc: var NbDoc): int = result = doc.id inc doc.id +func blocksFlattened*(doc: NbContainer): seq[NbBlock] = + for blk in doc.blocks: + result.add blk + if blk of NbContainer: + result.add blk.NbContainer.blocksFlattened() + # globals.nim var nbToJson: Table[string, proc (s: string, i: var int): NbBlock] var nbToHtml: NbRender # since we need it for json, let's make it also for html @@ -280,10 +286,9 @@ macro bumpGensym(n: static int) = return jscode proc nbCollectAllNbJs*(nb: var Nb) = - echo "In code!" var topCode = "" # placed at the top (nbJsFromCodeGlobal) var code = "" - for blk in nb.doc.blocks: # this won't work for containers, implement nb.flattenBlocks func + for blk in nb.doc.blocksFlattened: if blk of NbJsFromCode: let blk = blk.NbJsFromCode if blk.putAtTop: @@ -292,14 +297,13 @@ proc nbCollectAllNbJs*(nb: var Nb) = code.add "\n" & blk.transformedCode code = topCode & "\n" & code - print code if not code.isEmptyOrWhitespace: # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) let blk = NbJsFromCodeOwnFile(kind: "NbJsFromCodeOwnFile", code: "", transformedCode: code, showCode: false) nb.add blk # loop over all nbJsFromCodeOwnFile and compile them - for blk in nb.doc.blocks: + for blk in nb.doc.blocksFlattened: if blk of NbJsFromCodeOwnFile: var blk = blk.NbJsFromCodeOwnFile blk.jsCode = nb.compileNimToJs(blk) @@ -315,8 +319,9 @@ when isMainModule: nbText("42") nbCode: echo "hi" - nbJsFromCode: - echo "bye!" + nbDetails("Hide js code:"): + nbJsFromCode: + echo "bye!" nbSave #[ @@ -346,7 +351,7 @@ hi print docFromJson.blocks[0].NbDetails.blocks[0].NbText print docFromJson.blocks[0].NbDetails.blocks[1].NbImage print docFromJson.blocks[1].NbCode - print docFromJson.blocks[2].NbJsFromCode + print docFromJson.blocks[2].NbDetails.blocks[0].NbJsFromCode print docFromJson.blocks[3].NbJsFromCodeOwnFile #[ docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" From 591f8ed8053fde817f55248f131edb3b57f17668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:27:23 +0200 Subject: [PATCH 26/73] start working on sugar macro --- sandbox/minib/minib.nim | 82 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 7fc8448..4f689f8 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -1,5 +1,5 @@ # types.nim -import std/[tables, os, strformat, hashes] +import std/[tables, os, strformat, hashes, macros, genasts, strutils] import "$nim/compiler/pathutils" import print type @@ -79,6 +79,81 @@ template dumpKey(s: var string, v: string) = const v2 = v.toJson() & ":" s.add v2 +proc parseCallStmt(n: NimNode): tuple[lhs: string, rhs: NimNode] = + n.expectKind nnkCall + (n[0].strVal, n[1][0]) + +# nimibSugar.nim +macro newNbBlock(typeName: untyped, body: untyped): untyped = + # typeName is either `ident` or + # Infix + # Ident "of" + # Ident "nbImage" + # Ident "NbBlock" + + let (typeNameStr, parentType) = + if typeName.kind == nnkIdent: + (typeName.strVal, "NbBlock".ident) + else: + (typeName[1].strVal, typeName[2]) + + let capitalizedTypeName = typeNameStr.capitalizeAscii + + # body is: + # StmtList + # Call + # Ident "url" + # StmtList + # Ident "string" + # Call + # Ident "burl" + # StmtList + # Asgn + # Ident "int" + # IntLit 1 + # Call + # Ident "toHtml" + # StmtList + # DotExpr + # Ident "blk" + # Ident "url" + + echo "Body:\n", body.treeRepr + + var fields: seq[tuple[fieldName: string, fieldType: NimNode]] + var toHtmlBody: NimNode + body.expectKind(nnkStmtList) + for n in body: + let (name, body) = n.parseCallStmt() + if eqIdent(name, "toHtml"): + toHtmlBody = body + else: + fields.add (name, body) + + var fieldsList = nnkRecList.newTree() + for (fName, fType) in fields: + fieldsList.add newIdentDefs(fName.ident, fType) + let typeDefinition = nnkTypeSection.newTree(nnkTypeDef.newTree( + capitalizedTypeName.ident, + newEmptyNode(), # generic + nnkRefTy.newTree( + nnkObjectTy.newTree( + newEmptyNode(), # pragma + nnkOfInherit.newTree( + parentType + ), + fieldsList + ) + ) + )) + + echo "Type:\n", typeDefinition.repr + + # Next: generate initializer with prefilled kind + # Be vary of how we write the kind. SHould it be normalized or exactly like the user wrote it? + # It's more predictable if it is like the user wrote it + + assert false # themes.nim import std / strutils @@ -175,6 +250,11 @@ newNbBlock(nbImage): "" ]# +newNbBlock(nbImage): + url: string + toHtml: + "" + type NbDetails = ref object of NbContainer summary: string From a1c3d0b42c32e6057a726ecd4f1dead8e95c583c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Mon, 18 Nov 2024 19:32:26 +0100 Subject: [PATCH 27/73] fully implemented sugar --- sandbox/minib/minib.nim | 74 +++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 4f689f8..25acf18 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -1,5 +1,5 @@ # types.nim -import std/[tables, os, strformat, hashes, macros, genasts, strutils] +import std/[tables, os, strformat, hashes, macros, genasts, strutils, sequtils] import "$nim/compiler/pathutils" import print type @@ -98,6 +98,7 @@ macro newNbBlock(typeName: untyped, body: untyped): untyped = (typeName[1].strVal, typeName[2]) let capitalizedTypeName = typeNameStr.capitalizeAscii + let lowercasedTypeName = typeNameStr.toLower # body is: # StmtList @@ -151,9 +152,47 @@ macro newNbBlock(typeName: untyped, body: untyped): untyped = # Next: generate initializer with prefilled kind # Be vary of how we write the kind. SHould it be normalized or exactly like the user wrote it? - # It's more predictable if it is like the user wrote it + # It's more predictable if it is like the user wrote it. But more reasonable to normalize it... - assert false + var procParams = @[capitalizedTypeName.ident] & toSeq(fieldsList.children) + var objectConstructor = nnkObjConstr.newTree( + capitalizedTypeName.ident + ) + objectConstructor.add newColonExpr(ident"kind", capitalizedTypeName.newLit) + for (fName, _) in fields: + objectConstructor.add newColonExpr(fName.ident, fName.ident) + var procBody = newStmtList(objectConstructor) + + var procName = ident("new" & capitalizedTypeName) + let initializer = newProc(procName, procParams, procBody) + + echo "init:\n", initializer.repr + + # Next: generate nbImageToHtml from toHtmlBody + let renderProcName = (lowercasedTypeName & "ToHtml").ident + let renderProc = genAst(name = renderProcName, body = toHtmlBody, blk = ident"blk", nb = ident"nb", typeName=capitalizedTypeName.ident): + proc name*(blk: NbBlock, nb: Nb): string = + let blk = typeName(blk) + body + + + # Next: generate these lines: + # nbToHtml.funcs["NbImage"] = nbImageToHtml + # addNbBlockToJson(NbImage) + let hookAssignements = genAst(key = capitalizedTypeName.newLit, f = renderProcName, typeName = capitalizedTypeName.ident): + nbToHtml.funcs[key] = f + addNbBlockToJson(typeName) + + result = newStmtList( + typeDefinition, + initializer, + renderProc, + hookAssignements + ) + + echo result.repr + + #assert false # themes.nim import std / strutils @@ -232,17 +271,17 @@ newNbBlock(nbText): markdown(blk.text, config=initGfmConfig()) ]# -type - NbImage = ref object of NbBlock - url: string -template nbImage*(turl: string) = - let blk = NbImage(url: turl, kind: "NbImage") - nb.add blk -func nbImageToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbImage - "" -nbToHtml.funcs["NbImage"] = nbImageToHtml -addNbBlockToJson(NbImage) +# type +# NbImage = ref object of NbBlock +# url: string +# template nbImage*(turl: string) = +# let blk = NbImage(url: turl, kind: "NbImage") +# nb.add blk +# func nbImageToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbImage +# "" +# nbToHtml.funcs["NbImage"] = nbImageToHtml +# addNbBlockToJson(NbImage) #[ the above could be shortened with sugar to: newNbBlock(nbImage): url: string @@ -255,6 +294,13 @@ newNbBlock(nbImage): toHtml: "" +func image*(nb: var Nb, url: string) = + let blk = newNbImage(url=url) + nb.add blk + +template nbImage*(url: string) = + nb.image(url) + type NbDetails = ref object of NbContainer summary: string From 7fa59a8adcf2dffc41524b353578d44ec99782f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:15:11 +0100 Subject: [PATCH 28/73] rewrite nbText, nbDetails and nbCode using sugar --- sandbox/minib/minib.nim | 121 ++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 25acf18..977b488 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -251,25 +251,31 @@ template nbSave* = # all other blocks are in a sense all custom blocks # we could add sugar for common block creation -type - NbText = ref object of NbBlock - text: string -template nbText*(ttext: string) = - let blk = NbText(text: ttext, kind: "NbText") - nb.add blk -func nbTextToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbText - {.cast(noSideEffect).}: # not sure why markdown is marked with side effects - markdown(blk.text, config=initGfmConfig()) -nbToHtml.funcs["NbText"] = nbTextToHtml -addNbBlockToJson(NbText) -#[ the above could be shortened with sugar to: +# type +# NbText = ref object of NbBlock +# text: string +# template nbText*(ttext: string) = +# let blk = NbText(text: ttext, kind: "NbText") +# nb.add blk +# func nbTextToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbText +# {.cast(noSideEffect).}: # not sure why markdown is marked with side effects +# markdown(blk.text, config=initGfmConfig()) +# nbToHtml.funcs["NbText"] = nbTextToHtml +# addNbBlockToJson(NbText) + newNbBlock(nbText): text: string toHtml: {.cast(noSideEffect).}: # not sure why markdown is marked with side effects markdown(blk.text, config=initGfmConfig()) -]# + +proc text*(nb: var Nb, text: string) = + let blk = newNbText(text=text) + nb.add blk + +template nbText(ttext: string) = + nb.text(ttext) # type # NbImage = ref object of NbBlock @@ -301,40 +307,71 @@ func image*(nb: var Nb, url: string) = template nbImage*(url: string) = nb.image(url) -type - NbDetails = ref object of NbContainer - summary: string +# type +# NbDetails = ref object of NbContainer +# summary: string +# template nbDetails*(tsummary: string, body: untyped) = +# let blk = NbDetails(summary: tsummary, kind: "NbDetails") +# nb.withContainer(blk): +# body + +# func nbDetailsToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbDetails +# "
" & blk.summary & "\n" & +# nbContainerToHtml(blk, nb) & +# "\n
" + +# nbToHtml.funcs["NbDetails"] = nbDetailsToHtml +# addNbBlockToJson(NbDetails) + +newNbBlock(nbDetails of NbContainer): + summary: string + toHtml: + "
" & blk.summary & "\n" & + nbContainerToHtml(blk, nb) & + "\n
" + template nbDetails*(tsummary: string, body: untyped) = - let blk = NbDetails(summary: tsummary, kind: "NbDetails") + let blk = newNbDetails(summary=tsummary) nb.withContainer(blk): body -func nbDetailsToHtml*(blk: NbBlock, nb: Nb): string = - let blk = blk.NbDetails - "
" & blk.summary & "\n" & - nbContainerToHtml(blk, nb) & - "\n
" - -nbToHtml.funcs["NbDetails"] = nbDetailsToHtml -addNbBlockToJson(NbDetails) +# type +# NbCode = ref object of NbBlock +# code: string +# output: string +# lang: string +# func nbCodeToHtml(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbCode +# "
\n" &
+#   blk.code & '\n' &
+#   "
\n" & +# "
\n" &
+#   blk.output & '\n' &
+#   "
" +# nbToHtml.funcs["NbCode"] = nbCodeToHtml +# addNbBlockToJson(NbCode) +# template nbCode*(body: untyped) = +# let blk = NbCode(lang: "nim", kind: "NbCode") +# nb.add blk +# blk.code = toStr(body) +# captureStdout(blk.output): +# body + +newNbBlock(nbCode): + code: string + output: string + lang: string + toHtml: + "
\n" &
+    blk.code & '\n' &
+    "
\n" & + "
\n" &
+    blk.output & '\n' &
+    "
" -type - NbCode = ref object of NbBlock - code: string - output: string - lang: string -func nbCodeToHtml(blk: NbBlock, nb: Nb): string = - let blk = blk.NbCode - "
\n" &
-  blk.code & '\n' &
-  "
\n" & - "
\n" &
-  blk.output & '\n' &
-  "
" -nbToHtml.funcs["NbCode"] = nbCodeToHtml -addNbBlockToJson(NbCode) template nbCode*(body: untyped) = - let blk = NbCode(lang: "nim", kind: "NbCode") + let blk = newNbCode(code="", output="", lang="nim") nb.add blk blk.code = toStr(body) captureStdout(blk.output): From 571f22e1a7d6012d72ebc851d9bcd61f891e9a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:53:18 +0100 Subject: [PATCH 29/73] rewrite nbJs to sugar and write functional variants --- sandbox/minib/minib.nim | 1114 ++++++++++++++++++++------------------- 1 file changed, 579 insertions(+), 535 deletions(-) diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index 977b488..d1feb5e 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -1,535 +1,579 @@ -# types.nim -import std/[tables, os, strformat, hashes, macros, genasts, strutils, sequtils] -import "$nim/compiler/pathutils" -import print -type - NbBlock = ref object of RootObj - kind: string - NbRenderFunc = proc (blk: NbBlock, nb: Nb): string {. noSideEffect .} - NbRender = object - funcs: Table[string, NbRenderFunc] - NbContainer = ref object of NbBlock - blocks: seq[NbBlock] - NbDoc = ref object of NbContainer - title: string - id: int - nbJsCounter: int - Nb = object - blk: NbBlock # last block processed - doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc - containers: seq[NbContainer] # current container - backend: NbRender # current backend - -# this needs to be know for all container blocks not sure whether to put it in types -func render(nb: Nb, blk: NbBlock): string = - if blk.kind in nb.backend.funcs: - nb.backend.funcs[blk.kind](blk, nb) - else: - "" - -proc newId*(doc: var NbDoc): int = - ## Provides a unique integer each time it is called - result = doc.id - inc doc.id - -func blocksFlattened*(doc: NbContainer): seq[NbBlock] = - for blk in doc.blocks: - result.add blk - if blk of NbContainer: - result.add blk.NbContainer.blocksFlattened() - -# globals.nim -var nbToJson: Table[string, proc (s: string, i: var int): NbBlock] -var nbToHtml: NbRender # since we need it for json, let's make it also for html - -# jsons.nim -import jsony - -template addNbBlockToJson*(kind: untyped) = - nbToJson[$kind] = - proc (s: string, i: var int): NbBlock = - var v: kind - new v - parseHook(s, i, v[]) - result = v - - method dump(n: kind): string = - n[].toJson() - -proc parseHook*(s: string, i: var int, v: var NbBlock) = - # First parse the typename - var n: NbBlock = NbBlock() - let current_i = i - parseHook(s, i, n[]) - # Reset i - i = current_i - # Parse the correct type - let kind = n.kind - if kind notIn nbToJson: - raise ValueError.newException "cannot find kind in nbToJson: \"" & kind & '"' - v = nbToJson[kind](s, i) - -method dump(n: NbBlock): string = - n[].toJson() - -proc dumpHook*(s: var string, v: NbBlock) = - s.add v.dump() - -template dumpKey(s: var string, v: string) = - const v2 = v.toJson() & ":" - s.add v2 - -proc parseCallStmt(n: NimNode): tuple[lhs: string, rhs: NimNode] = - n.expectKind nnkCall - (n[0].strVal, n[1][0]) - -# nimibSugar.nim -macro newNbBlock(typeName: untyped, body: untyped): untyped = - # typeName is either `ident` or - # Infix - # Ident "of" - # Ident "nbImage" - # Ident "NbBlock" - - let (typeNameStr, parentType) = - if typeName.kind == nnkIdent: - (typeName.strVal, "NbBlock".ident) - else: - (typeName[1].strVal, typeName[2]) - - let capitalizedTypeName = typeNameStr.capitalizeAscii - let lowercasedTypeName = typeNameStr.toLower - - # body is: - # StmtList - # Call - # Ident "url" - # StmtList - # Ident "string" - # Call - # Ident "burl" - # StmtList - # Asgn - # Ident "int" - # IntLit 1 - # Call - # Ident "toHtml" - # StmtList - # DotExpr - # Ident "blk" - # Ident "url" - - echo "Body:\n", body.treeRepr - - var fields: seq[tuple[fieldName: string, fieldType: NimNode]] - var toHtmlBody: NimNode - body.expectKind(nnkStmtList) - for n in body: - let (name, body) = n.parseCallStmt() - if eqIdent(name, "toHtml"): - toHtmlBody = body - else: - fields.add (name, body) - - var fieldsList = nnkRecList.newTree() - for (fName, fType) in fields: - fieldsList.add newIdentDefs(fName.ident, fType) - let typeDefinition = nnkTypeSection.newTree(nnkTypeDef.newTree( - capitalizedTypeName.ident, - newEmptyNode(), # generic - nnkRefTy.newTree( - nnkObjectTy.newTree( - newEmptyNode(), # pragma - nnkOfInherit.newTree( - parentType - ), - fieldsList - ) - ) - )) - - echo "Type:\n", typeDefinition.repr - - # Next: generate initializer with prefilled kind - # Be vary of how we write the kind. SHould it be normalized or exactly like the user wrote it? - # It's more predictable if it is like the user wrote it. But more reasonable to normalize it... - - var procParams = @[capitalizedTypeName.ident] & toSeq(fieldsList.children) - var objectConstructor = nnkObjConstr.newTree( - capitalizedTypeName.ident - ) - objectConstructor.add newColonExpr(ident"kind", capitalizedTypeName.newLit) - for (fName, _) in fields: - objectConstructor.add newColonExpr(fName.ident, fName.ident) - var procBody = newStmtList(objectConstructor) - - var procName = ident("new" & capitalizedTypeName) - let initializer = newProc(procName, procParams, procBody) - - echo "init:\n", initializer.repr - - # Next: generate nbImageToHtml from toHtmlBody - let renderProcName = (lowercasedTypeName & "ToHtml").ident - let renderProc = genAst(name = renderProcName, body = toHtmlBody, blk = ident"blk", nb = ident"nb", typeName=capitalizedTypeName.ident): - proc name*(blk: NbBlock, nb: Nb): string = - let blk = typeName(blk) - body - - - # Next: generate these lines: - # nbToHtml.funcs["NbImage"] = nbImageToHtml - # addNbBlockToJson(NbImage) - let hookAssignements = genAst(key = capitalizedTypeName.newLit, f = renderProcName, typeName = capitalizedTypeName.ident): - nbToHtml.funcs[key] = f - addNbBlockToJson(typeName) - - result = newStmtList( - typeDefinition, - initializer, - renderProc, - hookAssignements - ) - - echo result.repr - - #assert false - -# themes.nim -import std / strutils - -func nbContainerToHtml(blk: NbBlock, nb: Nb): string = - let blk = blk.NbContainer - for b in blk.blocks: - result.add nb.render(b).strip & '\n' - result.strip -# should I add this to the global object? - -func nbDocToHtml*(blk: NbBlock, nb: Nb): string = - "\n" & - "" & blk.NbDoc.title & "\n" & - nbContainerToHtml(blk, nb) - -addNbBlockToJson(NbDoc) - -# blocks.nim -import std / macros - -proc add(nb: var Nb, blk: NbBlock) = - nb.blk = blk - if nb.containers.len == 0: - nb.doc.blocks.add blk - else: - nb.containers[^1].blocks.add blk - -template withContainer(nb: var Nb, container: NbContainer, body: untyped) = - nb.add container - nb.containers.add container - body - discard nb.containers.pop - -macro toStr*(body: untyped): string = - (body.toStrLit) - -# capture.nim -import capture # imported from old nimib (new nimib does not run locally for me!) - -# nimib.nim -import markdown -import jsutils - -template nbInit* = - var nb {. inject .}: Nb - nb.doc = NbDoc(kind: "NbDoc", title: "a nimib document") - nbToHtml.funcs["NbDoc"] = nbDocToHtml - nb.backend = nbToHtml - -template nbSave* = - echo "saving" - nbCollectAllNbJs(nb) - echo nb.render nb.doc - writeFile("test.html", nb.render(nb.doc)) - -# all other blocks are in a sense all custom blocks -# we could add sugar for common block creation -# type -# NbText = ref object of NbBlock -# text: string -# template nbText*(ttext: string) = -# let blk = NbText(text: ttext, kind: "NbText") -# nb.add blk -# func nbTextToHtml*(blk: NbBlock, nb: Nb): string = -# let blk = blk.NbText -# {.cast(noSideEffect).}: # not sure why markdown is marked with side effects -# markdown(blk.text, config=initGfmConfig()) -# nbToHtml.funcs["NbText"] = nbTextToHtml -# addNbBlockToJson(NbText) - -newNbBlock(nbText): - text: string - toHtml: - {.cast(noSideEffect).}: # not sure why markdown is marked with side effects - markdown(blk.text, config=initGfmConfig()) - -proc text*(nb: var Nb, text: string) = - let blk = newNbText(text=text) - nb.add blk - -template nbText(ttext: string) = - nb.text(ttext) - -# type -# NbImage = ref object of NbBlock -# url: string -# template nbImage*(turl: string) = -# let blk = NbImage(url: turl, kind: "NbImage") -# nb.add blk -# func nbImageToHtml*(blk: NbBlock, nb: Nb): string = -# let blk = blk.NbImage -# "" -# nbToHtml.funcs["NbImage"] = nbImageToHtml -# addNbBlockToJson(NbImage) -#[ the above could be shortened with sugar to: -newNbBlock(nbImage): - url: string - toHtml: - "" -]# - -newNbBlock(nbImage): - url: string - toHtml: - "" - -func image*(nb: var Nb, url: string) = - let blk = newNbImage(url=url) - nb.add blk - -template nbImage*(url: string) = - nb.image(url) - -# type -# NbDetails = ref object of NbContainer -# summary: string -# template nbDetails*(tsummary: string, body: untyped) = -# let blk = NbDetails(summary: tsummary, kind: "NbDetails") -# nb.withContainer(blk): -# body - -# func nbDetailsToHtml*(blk: NbBlock, nb: Nb): string = -# let blk = blk.NbDetails -# "
" & blk.summary & "\n" & -# nbContainerToHtml(blk, nb) & -# "\n
" - -# nbToHtml.funcs["NbDetails"] = nbDetailsToHtml -# addNbBlockToJson(NbDetails) - -newNbBlock(nbDetails of NbContainer): - summary: string - toHtml: - "
" & blk.summary & "\n" & - nbContainerToHtml(blk, nb) & - "\n
" - -template nbDetails*(tsummary: string, body: untyped) = - let blk = newNbDetails(summary=tsummary) - nb.withContainer(blk): - body - -# type -# NbCode = ref object of NbBlock -# code: string -# output: string -# lang: string -# func nbCodeToHtml(blk: NbBlock, nb: Nb): string = -# let blk = blk.NbCode -# "
\n" &
-#   blk.code & '\n' &
-#   "
\n" & -# "
\n" &
-#   blk.output & '\n' &
-#   "
" -# nbToHtml.funcs["NbCode"] = nbCodeToHtml -# addNbBlockToJson(NbCode) -# template nbCode*(body: untyped) = -# let blk = NbCode(lang: "nim", kind: "NbCode") -# nb.add blk -# blk.code = toStr(body) -# captureStdout(blk.output): -# body - -newNbBlock(nbCode): - code: string - output: string - lang: string - toHtml: - "
\n" &
-    blk.code & '\n' &
-    "
\n" & - "
\n" &
-    blk.output & '\n' &
-    "
" - -template nbCode*(body: untyped) = - let blk = newNbCode(code="", output="", lang="nim") - nb.add blk - blk.code = toStr(body) - captureStdout(blk.output): - body - -type - NbJsFromCode = ref object of NbBlock - code: string - transformedCode: string - putAtTop: bool - showCode: bool -func nbJsFromCodeToHtml(blk: NbBlock, nb: Nb): string = - let blk = blk.NbJsFromCode - if blk.showCode: - "
\n" & blk.code & "\n
" - else: - "" -nbToHtml.funcs["NbJsFromCode"] = nbJsFromCodeToHtml -addNbBlockToJson(NbJsFromCode) -template nbJsFromCode*(args: varargs[untyped]) = - let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) - let blk = NbJsFromCode(code: originalCode, transformedCode: transformedCode, putAtTop: false, showCode: false, kind: "NbJsFromCode") - nb.add blk - -type - NbJsFromCodeOwnFile = ref object of NbBlock - code: string - transformedCode: string - jsCode: string - showCode: bool -func nbJsFromCodeOwnFileToHtml(blk: NbBlock, nb: Nb): string = - let blk = blk.NbJsFromCodeOwnFile - result = - if blk.showCode: - "
\n" & blk.code & "\n
\n" - else: - "" - result &= &"" -nbToHtml.funcs["NbJsFromCodeOwnFile"] = nbJsFromCodeOwnFileToHtml -addNbBlockToJson(NbJsFromCodeOwnFile) -template nbJsFromCodeOwnFile*(args: varargs[untyped]) = - let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) - let blk = NbJsFromCodeOwnFile(code: originalCode, transformedCode: transformedCode, showCode: false, kind: "NbJsFromCodeOwnFile") - nb.add blk - -# jsutils.nim (these currently require to know of NbJsFromCode and NbJsFromCodeOwnFile...) -proc compileNimToJs*(nb: var Nb, blk: NbBlock): string = - let blk = blk.NbJsFromCodeOwnFile - let tempdir = getTempDir() / "nimib" - createDir(tempdir) - let (dir, filename, ext) = (getCurrentDir().AbsoluteDir, "js_file", ".nim")#doc.thisFile.splitFile() - let nimfile = dir / (filename & "_nbCodeToJs_" & $nb.doc.newId() & ext).RelativeFile - let jsfile = tempdir / &"out{hash(nb.doc.title)}.js" - var codeText = blk.transformedCode - let nbJsCounter = nb.doc.nbJsCounter - nb.doc.nbJsCounter += 1 - var bumpGensymString = """ -import std/[macros, json] - -macro bumpGensym(n: static int) = - for i in 0 .. n: - let _ = gensym() - -""" - bumpGensymString.add &"bumpGensym({nbJsCounter})\n" - codeText = bumpGensymString & codeText - writeFile(nimfile, codeText) - let kxiname = "nimib_kxi_" & $nb.doc.newId() - let errorCode = execShellCmd(&"nim js -d:danger -d:kxiname=\"{kxiname}\" -o:{jsfile} {nimfile}") - if errorCode != 0: - raise newException(OSError, "The compilation of a javascript file failed! Did you remember to capture all needed variables?\n" & $nimfile) - removeFile(nimfile) - let jscode = readFile(jsfile) - removeFile(jsfile) - return jscode - -proc nbCollectAllNbJs*(nb: var Nb) = - var topCode = "" # placed at the top (nbJsFromCodeGlobal) - var code = "" - for blk in nb.doc.blocksFlattened: - if blk of NbJsFromCode: - let blk = blk.NbJsFromCode - if blk.putAtTop: - topCode.add "\n" & blk.transformedCode - else: - code.add "\n" & blk.transformedCode - code = topCode & "\n" & code - - if not code.isEmptyOrWhitespace: - # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) - let blk = NbJsFromCodeOwnFile(kind: "NbJsFromCodeOwnFile", code: "", transformedCode: code, showCode: false) - nb.add blk - - # loop over all nbJsFromCodeOwnFile and compile them - for blk in nb.doc.blocksFlattened: - if blk of NbJsFromCodeOwnFile: - var blk = blk.NbJsFromCodeOwnFile - blk.jsCode = nb.compileNimToJs(blk) - - -when isMainModule: - import print - nbInit - nbDetails("Click for details:"): - nbText: "*hi*" - nbImage("img.png") - nbDetails("go deeper"): - nbText("42") - nbCode: - echo "hi" - nbDetails("Hide js code:"): - nbJsFromCode: - echo "bye!" - nbSave - #[ - -a nimib document<title> -<details><summary>Click for details:</summary> -<p><em>hi</em></p> -<img src= 'img.png'> -<details><summary>go deeper</summary> -<p>42</p> -</details> -</details> -<pre><code class="nim"> - -echo "hi" -</code></pre> -<pre> -hi - -</pre> - ]# - - let docToJson = nb.doc.toJson() - print docToJson - let docFromJson = docToJson.fromJson(NbDoc) # but now this fails - print docFromJson - print docFromJson.blocks[0].NbDetails - print docFromJson.blocks[0].NbDetails.blocks[0].NbText - print docFromJson.blocks[0].NbDetails.blocks[1].NbImage - print docFromJson.blocks[1].NbCode - print docFromJson.blocks[2].NbDetails.blocks[0].NbJsFromCode - print docFromJson.blocks[3].NbJsFromCodeOwnFile - #[ - docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" -docFromJson=NbDoc:ObjectType( - title: "a nimib document", - blocks: @[NbBlock:ObjectType(kind: "NbDetails"), NbBlock:ObjectType(kind: "NbCode")], - kind: "NbDoc" -) -docFromJson.blocks[0].NbDetails=NbDetails:ObjectType( - summary: "Click for details:", - blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage"), NbBlock:ObjectType(kind: "NbDetails")], - kind: "NbDetails" -) -docFromJson.blocks[0].NbDetails.blocks[0].NbText=NbText:ObjectType(text: "*hi*", kind: "NbText") -docFromJson.blocks[0].NbDetails.blocks[1].NbImage=NbImage:ObjectType(url: "img.png", kind: "NbImage") -docFromJson.blocks[1].NbCode=NbCode:ObjectType(code: "\necho "hi"", output: "hi\n", lang: "nim", kind: "NbCode") - ]# - +# types.nim +import std/[tables, os, strformat, hashes, macros, genasts, strutils, sequtils] +import "$nim/compiler/pathutils" +import print +type + NbBlock = ref object of RootObj + kind: string + NbRenderFunc = proc (blk: NbBlock, nb: Nb): string {. noSideEffect .} + NbRender = object + funcs: Table[string, NbRenderFunc] + NbContainer = ref object of NbBlock + blocks: seq[NbBlock] + NbDoc = ref object of NbContainer + title: string + id: int + nbJsCounter: int + Nb = object + blk: NbBlock # last block processed + doc: NbDoc # could be a NbBlock but we could give more guarantees with a NbDoc + containers: seq[NbContainer] # current container + backend: NbRender # current backend + +# this needs to be know for all container blocks not sure whether to put it in types +func render(nb: Nb, blk: NbBlock): string = + if blk.kind in nb.backend.funcs: + nb.backend.funcs[blk.kind](blk, nb) + else: + "" + +proc newId*(doc: var NbDoc): int = + ## Provides a unique integer each time it is called + result = doc.id + inc doc.id + +func blocksFlattened*(doc: NbContainer): seq[NbBlock] = + for blk in doc.blocks: + result.add blk + if blk of NbContainer: + result.add blk.NbContainer.blocksFlattened() + +# globals.nim +var nbToJson: Table[string, proc (s: string, i: var int): NbBlock] +var nbToHtml: NbRender # since we need it for json, let's make it also for html + +# jsons.nim +import jsony + +template addNbBlockToJson*(kind: untyped) = + nbToJson[$kind] = + proc (s: string, i: var int): NbBlock = + var v: kind + new v + parseHook(s, i, v[]) + result = v + + method dump(n: kind): string = + n[].toJson() + +proc parseHook*(s: string, i: var int, v: var NbBlock) = + # First parse the typename + var n: NbBlock = NbBlock() + let current_i = i + parseHook(s, i, n[]) + # Reset i + i = current_i + # Parse the correct type + let kind = n.kind + if kind notIn nbToJson: + raise ValueError.newException "cannot find kind in nbToJson: \"" & kind & '"' + v = nbToJson[kind](s, i) + +method dump(n: NbBlock): string = + n[].toJson() + +proc dumpHook*(s: var string, v: NbBlock) = + s.add v.dump() + +template dumpKey(s: var string, v: string) = + const v2 = v.toJson() & ":" + s.add v2 + +proc parseCallStmt(n: NimNode): tuple[lhs: string, rhs: NimNode] = + n.expectKind nnkCall + (n[0].strVal, n[1][0]) + +# nimibSugar.nim +macro newNbBlock(typeName: untyped, body: untyped): untyped = + # typeName is either `ident` or + # Infix + # Ident "of" + # Ident "nbImage" + # Ident "NbBlock" + + let (typeNameStr, parentType) = + if typeName.kind == nnkIdent: + (typeName.strVal, "NbBlock".ident) + else: + (typeName[1].strVal, typeName[2]) + + let capitalizedTypeName = typeNameStr.capitalizeAscii + let lowercasedTypeName = typeNameStr.toLower + + # body is: + # StmtList + # Call + # Ident "url" + # StmtList + # Ident "string" + # Call + # Ident "burl" + # StmtList + # Asgn + # Ident "int" + # IntLit 1 + # Call + # Ident "toHtml" + # StmtList + # DotExpr + # Ident "blk" + # Ident "url" + + echo "Body:\n", body.treeRepr + + var fields: seq[tuple[fieldName: string, fieldType: NimNode]] + var toHtmlBody: NimNode + body.expectKind(nnkStmtList) + for n in body: + let (name, body) = n.parseCallStmt() + if eqIdent(name, "toHtml"): + toHtmlBody = body + else: + fields.add (name, body) + + var fieldsList = nnkRecList.newTree() + for (fName, fType) in fields: + fieldsList.add newIdentDefs(fName.ident, fType) + let typeDefinition = nnkTypeSection.newTree(nnkTypeDef.newTree( + capitalizedTypeName.ident, + newEmptyNode(), # generic + nnkRefTy.newTree( + nnkObjectTy.newTree( + newEmptyNode(), # pragma + nnkOfInherit.newTree( + parentType + ), + fieldsList + ) + ) + )) + + echo "Type:\n", typeDefinition.repr + + # Next: generate initializer with prefilled kind + # Be vary of how we write the kind. SHould it be normalized or exactly like the user wrote it? + # It's more predictable if it is like the user wrote it. But more reasonable to normalize it... + + var procParams = @[capitalizedTypeName.ident] & toSeq(fieldsList.children) + var objectConstructor = nnkObjConstr.newTree( + capitalizedTypeName.ident + ) + objectConstructor.add newColonExpr(ident"kind", capitalizedTypeName.newLit) + for (fName, _) in fields: + objectConstructor.add newColonExpr(fName.ident, fName.ident) + var procBody = newStmtList(objectConstructor) + + var procName = ident("new" & capitalizedTypeName) + let initializer = newProc(procName, procParams, procBody) + + echo "init:\n", initializer.repr + + # Next: generate nbImageToHtml from toHtmlBody + let renderProcName = (lowercasedTypeName & "ToHtml").ident + let renderProc = genAst(name = renderProcName, body = toHtmlBody, blk = ident"blk", nb = ident"nb", typeName=capitalizedTypeName.ident): + proc name*(blk: NbBlock, nb: Nb): string = + let blk = typeName(blk) + body + + + # Next: generate these lines: + # nbToHtml.funcs["NbImage"] = nbImageToHtml + # addNbBlockToJson(NbImage) + # should we make this into a proc instead so it can be used in the non-sugar variant as well? + let hookAssignements = genAst(key = capitalizedTypeName.newLit, f = renderProcName, typeName = capitalizedTypeName.ident): + nbToHtml.funcs[key] = f + addNbBlockToJson(typeName) + + result = newStmtList( + typeDefinition, + initializer, + renderProc, + hookAssignements + ) + + echo result.repr + + #assert false + +# themes.nim +import std / strutils + +func nbContainerToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbContainer + for b in blk.blocks: + result.add nb.render(b).strip & '\n' + result.strip +# should I add this to the global object? + +func nbDocToHtml*(blk: NbBlock, nb: Nb): string = + "<!DOCTYPE html>\n" & + "<title>" & blk.NbDoc.title & "\n" & + nbContainerToHtml(blk, nb) + +addNbBlockToJson(NbDoc) + +# blocks.nim +import std / macros + +proc add(nb: var Nb, blk: NbBlock) = + nb.blk = blk + if nb.containers.len == 0: + nb.doc.blocks.add blk + else: + nb.containers[^1].blocks.add blk + +template withContainer(nb: var Nb, container: NbContainer, body: untyped) = + nb.add container + nb.containers.add container + body + discard nb.containers.pop + +macro toStr*(body: untyped): string = + (body.toStrLit) + +# capture.nim +import capture # imported from old nimib (new nimib does not run locally for me!) + +# nimib.nim +import markdown +import jsutils + +template nbInit* = + var nb {. inject .}: Nb + nb.doc = NbDoc(kind: "NbDoc", title: "a nimib document") + nbToHtml.funcs["NbDoc"] = nbDocToHtml + nb.backend = nbToHtml + +template nbSave* = + echo "saving" + nbCollectAllNbJs(nb) + echo nb.render nb.doc + writeFile("test.html", nb.render(nb.doc)) + +# all other blocks are in a sense all custom blocks +# we could add sugar for common block creation +# type +# NbText = ref object of NbBlock +# text: string +# template nbText*(ttext: string) = +# let blk = NbText(text: ttext, kind: "NbText") +# nb.add blk +# func nbTextToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbText +# {.cast(noSideEffect).}: # not sure why markdown is marked with side effects +# markdown(blk.text, config=initGfmConfig()) +# nbToHtml.funcs["NbText"] = nbTextToHtml +# addNbBlockToJson(NbText) + +newNbBlock(nbText): + text: string + toHtml: + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(blk.text, config=initGfmConfig()) + +proc text*(nb: var Nb, text: string) = + let blk = newNbText(text=text) + nb.add blk + +template nbText(ttext: string) = + nb.text(ttext) + +# type +# NbImage = ref object of NbBlock +# url: string +# template nbImage*(turl: string) = +# let blk = NbImage(url: turl, kind: "NbImage") +# nb.add blk +# func nbImageToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbImage +# "" +# nbToHtml.funcs["NbImage"] = nbImageToHtml +# addNbBlockToJson(NbImage) +#[ the above could be shortened with sugar to: +newNbBlock(nbImage): + url: string + toHtml: + "" +]# + +newNbBlock(nbImage): + url: string + toHtml: + "" + +func image*(nb: var Nb, url: string) = + let blk = newNbImage(url=url) + nb.add blk + +template nbImage*(url: string) = + nb.image(url) + +# type +# NbDetails = ref object of NbContainer +# summary: string +# template nbDetails*(tsummary: string, body: untyped) = +# let blk = NbDetails(summary: tsummary, kind: "NbDetails") +# nb.withContainer(blk): +# body + +# func nbDetailsToHtml*(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbDetails +# "
" & blk.summary & "\n" & +# nbContainerToHtml(blk, nb) & +# "\n
" + +# nbToHtml.funcs["NbDetails"] = nbDetailsToHtml +# addNbBlockToJson(NbDetails) + +newNbBlock(nbDetails of NbContainer): + summary: string + toHtml: + "
" & blk.summary & "\n" & + nbContainerToHtml(blk, nb) & + "\n
" + +template details*(nb: var Nb, tsummary: string, body: untyped) = + let blk = newNbDetails(summary=tsummary) + nb.withContainer(blk): + body + +template nbDetails*(tsummary: string, body: untyped) = + nb.details(tsummary): + body + +# type +# NbCode = ref object of NbBlock +# code: string +# output: string +# lang: string +# func nbCodeToHtml(blk: NbBlock, nb: Nb): string = +# let blk = blk.NbCode +# "
\n" &
+#   blk.code & '\n' &
+#   "
\n" & +# "
\n" &
+#   blk.output & '\n' &
+#   "
" +# nbToHtml.funcs["NbCode"] = nbCodeToHtml +# addNbBlockToJson(NbCode) +# template nbCode*(body: untyped) = +# let blk = NbCode(lang: "nim", kind: "NbCode") +# nb.add blk +# blk.code = toStr(body) +# captureStdout(blk.output): +# body + +newNbBlock(nbCode): + code: string + output: string + lang: string + toHtml: + "
\n" &
+    blk.code & '\n' &
+    "
\n" & + "
\n" &
+    blk.output & '\n' &
+    "
" + +template code*(nb: var Nb, body: untyped) = + let blk = newNbCode(code="", output="", lang="nim") + nb.add blk + blk.code = toStr(body) + captureStdout(blk.output): + body + +template nbCode*(body: untyped) = + nb.code: + body + +#[ type + NbJsFromCode = ref object of NbBlock + code: string + transformedCode: string + putAtTop: bool + showCode: bool +func nbJsFromCodeToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbJsFromCode + if blk.showCode: + "
\n" & blk.code & "\n
" + else: + "" +nbToHtml.funcs["NbJsFromCode"] = nbJsFromCodeToHtml +addNbBlockToJson(NbJsFromCode) ]# + +newNbBlock(NbJsFromCode): + code: string + transformedCode: string + putAtTop: bool + showCode: bool + toHtml: + if blk.showCode: + "
\n" & blk.code & "\n
" + else: + "" + +template jsFromCode*(nb: var Nb, args: varargs[untyped]) = + let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = newNbJsFromCode(code=originalCode, transformedCode=transformedCode, putAtTop=false, showCode=false) + nb.add blk + +template nbJsFromCode*(args: varargs[untyped]) = + nb.jsFromCode(args) + +#[ type + NbJsFromCodeOwnFile = ref object of NbBlock + code: string + transformedCode: string + jsCode: string + showCode: bool +func nbJsFromCodeOwnFileToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbJsFromCodeOwnFile + result = + if blk.showCode: + "
\n" & blk.code & "\n
\n" + else: + "" + result &= &"" +nbToHtml.funcs["NbJsFromCodeOwnFile"] = nbJsFromCodeOwnFileToHtml +addNbBlockToJson(NbJsFromCodeOwnFile) ]# + +newNbBlock(NbJsFromCodeOwnFile): + code: string + transformedCode: string + jsCode: string + showCode: bool + toHtml: + result = + if blk.showCode: + "
\n" & blk.code & "\n
\n" + else: + "" + result &= &"" + +template jsFromCodeOwnFile*(nb: var Nb, args: varargs[untyped]) = + let (transformedCode, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = NbJsFromCodeOwnFile(code: originalCode, transformedCode: transformedCode, showCode: false, kind: "NbJsFromCodeOwnFile") + nb.add blk + +template nbJsFromCodeOwnFile*(args: varargs[untyped]) = + nb.jsFromCodeOwnFile(args) + +# jsutils.nim (these currently require to know of NbJsFromCode and NbJsFromCodeOwnFile...) +proc compileNimToJs*(nb: var Nb, blk: NbBlock): string = + let blk = blk.NbJsFromCodeOwnFile + let tempdir = getTempDir() / "nimib" + createDir(tempdir) + let (dir, filename, ext) = (getCurrentDir().AbsoluteDir, "js_file", ".nim")#doc.thisFile.splitFile() + let nimfile = dir / (filename & "_nbCodeToJs_" & $nb.doc.newId() & ext).RelativeFile + let jsfile = tempdir / &"out{hash(nb.doc.title)}.js" + var codeText = blk.transformedCode + let nbJsCounter = nb.doc.nbJsCounter + nb.doc.nbJsCounter += 1 + var bumpGensymString = """ +import std/[macros, json] + +macro bumpGensym(n: static int) = + for i in 0 .. n: + let _ = gensym() + +""" + bumpGensymString.add &"bumpGensym({nbJsCounter})\n" + codeText = bumpGensymString & codeText + writeFile(nimfile, codeText) + let kxiname = "nimib_kxi_" & $nb.doc.newId() + let errorCode = execShellCmd(&"nim js -d:danger -d:kxiname=\"{kxiname}\" -o:{jsfile} {nimfile}") + if errorCode != 0: + raise newException(OSError, "The compilation of a javascript file failed! Did you remember to capture all needed variables?\n" & $nimfile) + removeFile(nimfile) + let jscode = readFile(jsfile) + removeFile(jsfile) + return jscode + +proc nbCollectAllNbJs*(nb: var Nb) = + var topCode = "" # placed at the top (nbJsFromCodeGlobal) + var code = "" + for blk in nb.doc.blocksFlattened: + if blk of NbJsFromCode: + let blk = blk.NbJsFromCode + if blk.putAtTop: + topCode.add "\n" & blk.transformedCode + else: + code.add "\n" & blk.transformedCode + code = topCode & "\n" & code + + if not code.isEmptyOrWhitespace: + # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) + let blk = NbJsFromCodeOwnFile(kind: "NbJsFromCodeOwnFile", code: "", transformedCode: code, showCode: false) + nb.add blk + + # loop over all nbJsFromCodeOwnFile and compile them + for blk in nb.doc.blocksFlattened: + if blk of NbJsFromCodeOwnFile: + var blk = blk.NbJsFromCodeOwnFile + blk.jsCode = nb.compileNimToJs(blk) + + +when isMainModule: + import print + nbInit + nbDetails("Click for details:"): + nbText: "*hi*" + nbImage("img.png") + nbDetails("go deeper"): + nbText("42") + nbCode: + echo "hi" + nb.code: + echo "nb.code" + nbDetails("Hide js code:"): + nbJsFromCode: + echo "bye!" + nbSave + #[ + +a nimib document<title> +<details><summary>Click for details:</summary> +<p><em>hi</em></p> +<img src= 'img.png'> +<details><summary>go deeper</summary> +<p>42</p> +</details> +</details> +<pre><code class="nim"> + +echo "hi" +</code></pre> +<pre> +hi + +</pre> + ]# + + let docToJson = nb.doc.toJson() + print docToJson + let docFromJson = docToJson.fromJson(NbDoc) # but now this fails + print docFromJson + print docFromJson.blocks[0].NbDetails + print docFromJson.blocks[0].NbDetails.blocks[0].NbText + print docFromJson.blocks[0].NbDetails.blocks[1].NbImage + print docFromJson.blocks[1].NbCode + print docFromJson.blocks[2].NbCode + print docFromJson.blocks[3].NbDetails.blocks[0].NbJsFromCode + print docFromJson.blocks[4].NbJsFromCodeOwnFile + #[ + docToJson="{"title":"a nimib document","blocks":[{"summary":"Click for details:","blocks":[{"text":"*hi*","kind":"NbText"},{"url":"img.png","kind":"NbImage"},{"summary":"go deeper","blocks":[{"text":"42","kind":"NbText"}],"kind":"NbDetails"}],"kind":"NbDetails"},{"code":"\\necho \\"hi\\"","output":"hi\\n","lang":"nim","kind":"NbCode"}],"kind":"NbDoc"}" +docFromJson=NbDoc:ObjectType( + title: "a nimib document", + blocks: @[NbBlock:ObjectType(kind: "NbDetails"), NbBlock:ObjectType(kind: "NbCode")], + kind: "NbDoc" +) +docFromJson.blocks[0].NbDetails=NbDetails:ObjectType( + summary: "Click for details:", + blocks: @[NbBlock:ObjectType(kind: "NbText"), NbBlock:ObjectType(kind: "NbImage"), NbBlock:ObjectType(kind: "NbDetails")], + kind: "NbDetails" +) +docFromJson.blocks[0].NbDetails.blocks[0].NbText=NbText:ObjectType(text: "*hi*", kind: "NbText") +docFromJson.blocks[0].NbDetails.blocks[1].NbImage=NbImage:ObjectType(url: "img.png", kind: "NbImage") +docFromJson.blocks[1].NbCode=NbCode:ObjectType(code: "\necho "hi"", output: "hi\n", lang: "nim", kind: "NbCode") + ]# + From 9394c20293b5af0b7280cfa144d92556ed86ee32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Granstr=C3=B6m?= <5092565+HugoGranstrom@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:59:52 +0100 Subject: [PATCH 30/73] start the chaotic work of rewriting nimib --- sandbox/minib/minib.nim | 2 +- src/nimib.nim | 90 ++++++++++++-------- src/nimib/blocks.nim | 23 ++++- src/nimib/docs.nim | 8 +- src/nimib/globals.nim | 4 + src/nimib/jsons.nim | 37 +++++++++ src/nimib/jsutils.nim | 85 ++++++++++++++++++- src/nimib/nimibSugars.nim | 133 +++++++++++++++++++++++++++++ src/nimib/renders.nim | 19 +++-- src/nimib/themes.nim | 171 ++++++++++++++++++++++++++++++-------- src/nimib/types.nim | 49 +++++++---- 11 files changed, 517 insertions(+), 104 deletions(-) create mode 100644 src/nimib/globals.nim create mode 100644 src/nimib/jsons.nim create mode 100644 src/nimib/nimibSugars.nim diff --git a/sandbox/minib/minib.nim b/sandbox/minib/minib.nim index d1feb5e..316750d 100644 --- a/sandbox/minib/minib.nim +++ b/sandbox/minib/minib.nim @@ -265,7 +265,7 @@ template nbSave* = # nbToHtml.funcs["NbText"] = nbTextToHtml # addNbBlockToJson(NbText) -newNbBlock(nbText): +newNbBlock(NbText): text: string toHtml: {.cast(noSideEffect).}: # not sure why markdown is marked with side effects diff --git a/src/nimib.nim b/src/nimib.nim index b2be22e..55ed5ef 100644 --- a/src/nimib.nim +++ b/src/nimib.nim @@ -1,7 +1,9 @@ -import std/[os, strutils, sugar, strformat, macros, macrocache, sequtils, jsonutils] +import std/[os, strutils, sugar, strformat, macros, macrocache, sequtils, json] +import std / jsonutils except toJson export jsonutils -import nimib / [types, blocks, docs, boost, config, options, capture, jsutils] -export types, blocks, docs, boost, sugar, jsutils +import markdown +import nimib / [types, blocks, docs, boost, config, options, capture, jsons, globals, jsutils, nimibSugars] +export types, blocks, docs, boost, sugar, globals, nimibSugars, jsutils # types exports mustache, tables, paths from nimib / themes import nil @@ -15,48 +17,49 @@ export searchTable, searchDirs, castStr template moduleAvailable*(module: untyped): bool = (compiles do: import module) -template nbInit*(theme = themes.useDefault, backend = renders.useHtmlBackend, thisFileRel = "") = - var nb {.inject.}: NbDoc - nb.initDir = getCurrentDir().AbsoluteDir - loadOptions nb - loadCfg nb +template nbInit*(theme = themes.useDefault, renderer: NbRender = nbToHtml, thisFileRel = "") = + var nb {.inject.}: Nb + nb.doc = NbDoc(kind: "NbDoc") + nb.doc.initDir = getCurrentDir().AbsoluteDir + loadOptions nb.doc + loadCfg nb.doc # nbInit can be called not from inside the correct file (e.g. when rendering markdown files in nimibook) if thisFileRel == "": - nb.thisFile = instantiationInfo(-1, true).filename.AbsoluteFile + nb.doc.thisFile = instantiationInfo(-1, true).filename.AbsoluteFile else: - nb.thisFile = nb.srcDir / thisFileRel.RelativeFile - echo "[nimib] thisFile: ", nb.thisFile + nb.doc.thisFile = nb.doc.srcDir / thisFileRel.RelativeFile + echo "[nimib] thisFile: ", nb.doc.thisFile try: - nb.source = read(nb.thisFile) + nb.doc.source = read(nb.doc.thisFile) except IOError: echo "[nimib] cannot read source" - if nb.options.filename == "": - nb.filename = nb.thisFile.string.splitFile.name & ".html" + if nb.doc.options.filename == "": + nb.doc.filename = nb.doc.thisFile.string.splitFile.name & ".html" else: - nb.filename = nb.options.filename + nb.doc.filename = nb.doc.options.filename - if nb.cfg.srcDir != "": - echo "[nimib] srcDir: ", nb.srcDir - nb.filename = (nb.thisDir.relativeTo nb.srcDir).string / nb.filename - echo "[nimib] filename: ", nb.filename + if nb.doc.cfg.srcDir != "": + echo "[nimib] srcDir: ", nb.doc.srcDir + nb.doc.filename = (nb.doc.thisDir.relativeTo nb.doc.srcDir).string / nb.doc.filename + echo "[nimib] filename: ", nb.doc.filename - if nb.cfg.homeDir != "": - echo "[nimib] setting current directory to nb.homeDir: ", nb.homeDir - setCurrentDir nb.homeDir + if nb.doc.cfg.homeDir != "": + echo "[nimib] setting current directory to nb.doc.homeDir: ", nb.doc.homeDir + setCurrentDir nb.doc.homeDir # can be overriden by theme, but it is better to initialize this anyway - nb.templateDirs = @["./", "./templates/"] - nb.partials = initTable[string, string]() - nb.context = newContext(searchDirs = @[]) # templateDirs and partials added during nbSave + #nb.templateDirs = @["./", "./templates/"] + #nb.partials = initTable[string, string]() + nb.doc.context = newJObject() #newContext(searchDirs = @[]) # templateDirs and partials added during nbSave # apply render backend (default backend can be overriden by theme) - backend nb + nb.backend = renderer # apply theme - theme nb + theme nb.doc # how do we handle themes? template nbInitMd*(thisFileRel = "") = var tfr = if thisFileRel == "": @@ -102,9 +105,25 @@ template nimibCode*(body: untyped) = discard body -template nbText*(text: string) = +#[ template nbText*(text: string) = newNbSlimBlock("nbText"): - nb.blk.output = text + nb.blk.output = text ]# + +newNbBlock(NbText): + text: string + toHtml: + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(blk.text, config=initGfmConfig()) + +proc text*(nb: var Nb, text: string) = + let blk = newNbText(text=text) + nb.add blk + +template nbText*(ttext: string) = + nb.text(ttext) + + + template nbTextWithCode*(body: untyped) = newNbCodeBlock("nbText", body): @@ -264,21 +283,22 @@ template nbSave* = # - in case you need to manage additional exceptions for a specific document add a new set of partials before calling nbSave nb.nbCollectAllNbJs() - nb.context.searchDirs(nb.templateDirs) - nb.context.searchTable(nb.partials) + echo "nb.doc.blocks: ", nb.doc.blocks + #nb.context.searchDirs(nb.templateDirs) + #nb.context.searchTable(nb.partials) write nb - if nb.options.show: - open nb + if nb.doc.options.show: + open nb.doc # how to change this to a better version using nb? template relPath*(path: AbsoluteFile | AbsoluteDir): string = (path.relativeTo nb.homeDir).string # aliases to minimize breaking changes after refactoring nbDoc -> nb. Should be deprecated at some point? -template nbDoc*: NbDoc = nb +#[ template nbDoc*: NbDoc = nb template nbBlock*: NbBlock = nb.blk -template nbHomeDir*: AbsoluteDir = nb.homeDir +template nbHomeDir*: AbsoluteDir = nb.homeDir ]# # use --nbShow runtime option instead of this template nbShow* = diff --git a/src/nimib/blocks.nim b/src/nimib/blocks.nim index 7ba5665..3b92254 100644 --- a/src/nimib/blocks.nim +++ b/src/nimib/blocks.nim @@ -16,7 +16,28 @@ func nbNormalize*(text: string): string = text.replace("\c\l", "\n").replace("\c", "\n").strip # this could be made more efficient # note that: '\c' == '\r' and '\l' == '\n' -template newNbBlock*(cmd: string, readCode: static[bool], nbDoc, nbBlock, body, blockImpl: untyped) = +proc add*(nb: var Nb, blk: NbBlock) = + nb.blk = blk + if nb.containers.len == 0: + nb.doc.blocks.add blk + else: + nb.containers[^1].blocks.add blk + +template withContainer*(nb: var Nb, container: NbContainer, body: untyped) = + nb.add container + nb.containers.add container + body + discard nb.containers.pop + +func blocksFlattened*(doc: NbContainer): seq[NbBlock] = + for blk in doc.blocks: + result.add blk + if blk of NbContainer: + result.add blk.NbContainer.blocksFlattened() + +# do we need this anymore? How do we implement something similar now? I'm thinking mainly of the logging +# insert it into the newBlockName procs? +template newNbBlockOld*(cmd: string, readCode: static[bool], nbDoc, nbBlock, body, blockImpl: untyped) = stdout.write "[nimib] ", nbDoc.blocks.len, " ", cmd, ": " nbBlock = NbBlock(command: cmd, context: newContext(searchDirs = @[], partials = nbDoc.partials)) when readCode: diff --git a/src/nimib/docs.nim b/src/nimib/docs.nim index d1b5a85..81d1676 100644 --- a/src/nimib/docs.nim +++ b/src/nimib/docs.nim @@ -3,14 +3,14 @@ import browsers import types import nimib / renders -proc write*(doc: var NbDoc) = +proc write*(nb: Nb) = echo "[nimib] current directory: ", getCurrentDir() - let dir = doc.filename.splitFile().dir + let dir = nb.doc.filename.splitFile().dir if not dir.dirExists: echo "[nimib] creating directory: ", dir createDir(dir) - echo "[nimib] saving file: ", doc.filename - writeFile(doc.filename, render(doc)) + echo "[nimib] saving file: ", nb.doc.filename + writeFile(nb.doc.filename, nb.render(nb.doc)) proc open*(doc: NbDoc) = openDefaultBrowser(doc.filename) diff --git a/src/nimib/globals.nim b/src/nimib/globals.nim new file mode 100644 index 0000000..15ee10f --- /dev/null +++ b/src/nimib/globals.nim @@ -0,0 +1,4 @@ +import ./types + +var nbToJson*: Table[string, proc (s: string, i: var int): NbBlock] +var nbToHtml* = NbRender() # since we need it for json, let's make it also for html \ No newline at end of file diff --git a/src/nimib/jsons.nim b/src/nimib/jsons.nim new file mode 100644 index 0000000..5afcfe0 --- /dev/null +++ b/src/nimib/jsons.nim @@ -0,0 +1,37 @@ +import jsony +import ./types, ./globals +export jsony + +template addNbBlockToJson*(kind: untyped) = + nbToJson[$kind] = + proc (s: string, i: var int): NbBlock = + var v: kind + new v + parseHook(s, i, v[]) + result = v + + method dump*(n: kind): string = + jsony.toJson(n[]) + +proc parseHook*(s: string, i: var int, v: var NbBlock) = + # First parse the typename + var n: NbBlock = NbBlock() + let current_i = i + parseHook(s, i, n[]) + # Reset i + i = current_i + # Parse the correct type + let kind = n.kind + if kind notIn nbToJson: + raise ValueError.newException "cannot find kind in nbToJson: \"" & kind & '"' + v = nbToJson[kind](s, i) + +method dump*(n: NbBlock): string {.base.} = + jsony.toJson(n[]) + +proc dumpHook*(s: var string, v: NbBlock) = + s.add v.dump() + +template dumpKey*(s: var string, v: string) = + const v2 = jsony.toJson(v) & ":" + s.add v2 diff --git a/src/nimib/jsutils.nim b/src/nimib/jsutils.nim index 5e7d032..aaff0fb 100644 --- a/src/nimib/jsutils.nim +++ b/src/nimib/jsutils.nim @@ -1,5 +1,30 @@ import std / [macros, macrocache, tables, strutils, strformat, sequtils, sugar, os, hashes] -import ./types +import ./types, ./nimibSugars, ./globals, ./jsons, ./blocks + + +newNbBlock(NbJsFromCode): + code: string + transformedCode: string + putAtTop: bool + showCode: bool + toHtml: + if blk.showCode: + "<pre><code class=\"nim\">\n" & blk.code & "\n</code></pre>" + else: + "" + +newNbBlock(NbJsFromCodeOwnFile): + code: string + transformedCode: string + jsCode: string + showCode: bool + toHtml: + result = + if blk.showCode: + "<pre><code class=\"nim\">\n" & blk.code & "\n</code></pre>\n" + else: + "" + result &= &"<script>\n{blk.jsCode}\n</script>" proc contains(tab: CacheTable, keyToCheck: string): bool = for key, val in tab: @@ -179,7 +204,8 @@ macro nbHappyxCodeBackend*(rootId: untyped, args: varargs[untyped]) = let call = newCall(ident"nbJsFromCodeOwnFile", callArgs) result = call -proc compileNimToJs*(doc: var NbDoc, blk: var NbBlock) = +#[ proc compileNimToJs*(doc: var NbDoc, blk: var NbBlock) = + let blk = let tempdir = getTempDir() / "nimib" createDir(tempdir) let (dir, filename, ext) = doc.thisFile.splitFile() @@ -225,4 +251,57 @@ proc nbCollectAllNbJs*(doc: var NbDoc) = var blk = NbBlock(command: "nbJsFromCodeOwnFile", code: code, context: newContext(searchDirs = @[], partials = doc.partials), output: "") blk.context["transformedCode"] = code doc.blocks.add blk - doc.blk = blk + doc.blk = blk ]# + +proc compileNimToJs*(nb: var Nb, blk: NbBlock): string = + let blk = blk.NbJsFromCodeOwnFile + let tempdir = getTempDir() / "nimib" + createDir(tempdir) + let (dir, filename, ext) = (getCurrentDir().AbsoluteDir, "js_file", ".nim")#doc.thisFile.splitFile() + let nimfile = dir / (filename & "_nbCodeToJs_" & $nb.doc.newId() & ext).RelativeFile + let jsfile = tempdir / &"out{hash(nb.doc.thisFile)}.js" + var codeText = blk.transformedCode + let nbJsCounter = nb.doc.nbJsCounter + nb.doc.nbJsCounter += 1 + var bumpGensymString = """ +import std/[macros, json] + +macro bumpGensym(n: static int) = + for i in 0 .. n: + let _ = gensym() + +""" + bumpGensymString.add &"bumpGensym({nbJsCounter})\n" + codeText = bumpGensymString & codeText + writeFile(nimfile, codeText) + let kxiname = "nimib_kxi_" & $nb.doc.newId() + let errorCode = execShellCmd(&"nim js -d:danger -d:kxiname=\"{kxiname}\" -o:{jsfile} {nimfile}") + if errorCode != 0: + raise newException(OSError, "The compilation of a javascript file failed! Did you remember to capture all needed variables?\n" & $nimfile) + removeFile(nimfile) + let jscode = readFile(jsfile) + removeFile(jsfile) + return jscode + +proc nbCollectAllNbJs*(nb: var Nb) = + var topCode = "" # placed at the top (nbJsFromCodeGlobal) + var code = "" + for blk in nb.doc.blocksFlattened: + if blk of NbJsFromCode: + let blk = blk.NbJsFromCode + if blk.putAtTop: + topCode.add "\n" & blk.transformedCode + else: + code.add "\n" & blk.transformedCode + code = topCode & "\n" & code + + if not code.isEmptyOrWhitespace: + # Create block which which will compile the code when rendered (nbJsFromJsOwnFile) + let blk = NbJsFromCodeOwnFile(kind: "NbJsFromCodeOwnFile", code: "", transformedCode: code, showCode: false) + nb.add blk + + # loop over all nbJsFromCodeOwnFile and compile them + for blk in nb.doc.blocksFlattened: + if blk of NbJsFromCodeOwnFile: + var blk = blk.NbJsFromCodeOwnFile + blk.jsCode = nb.compileNimToJs(blk) diff --git a/src/nimib/nimibSugars.nim b/src/nimib/nimibSugars.nim new file mode 100644 index 0000000..5236660 --- /dev/null +++ b/src/nimib/nimibSugars.nim @@ -0,0 +1,133 @@ +import std / [macros, strutils, sequtils, genasts] + +proc parseCallStmt(n: NimNode): tuple[lhs: string, rhs: NimNode] = + n.expectKind nnkCall + (n[0].strVal, n[1][0]) + +macro newNbBlock*(typeName: untyped, body: untyped): untyped = + # typeName is either `ident` or + # Infix + # Ident "of" + # Ident "nbImage" + # Ident "NbBlock" + + let (typeNameStr, parentType) = + if typeName.kind == nnkIdent: + (typeName.strVal, "NbBlock".ident) + else: + (typeName[1].strVal, typeName[2]) + + let capitalizedTypeName = typeNameStr.capitalizeAscii + let lowercasedTypeName = typeNameStr.toLower + + # body is: + # StmtList + # Call + # Ident "url" + # StmtList + # Ident "string" + # Call + # Ident "burl" + # StmtList + # Asgn + # Ident "int" + # IntLit 1 + # Call + # Ident "toHtml" + # StmtList + # DotExpr + # Ident "blk" + # Ident "url" + + echo "Body:\n", body.treeRepr + + var fields: seq[tuple[fieldName: string, fieldType: NimNode]] + var toHtmlBody: NimNode + body.expectKind(nnkStmtList) + for n in body: + let (name, body) = n.parseCallStmt() + if eqIdent(name, "toHtml"): + toHtmlBody = body + else: + fields.add (name, body) + + var fieldsList = nnkRecList.newTree() + for (fName, fType) in fields: + fieldsList.add newIdentDefs(fName.ident, fType) + let typeDefinition = nnkTypeSection.newTree(nnkTypeDef.newTree( + postfix(capitalizedTypeName.ident, "*"), + newEmptyNode(), # generic + nnkRefTy.newTree( + nnkObjectTy.newTree( + newEmptyNode(), # pragma + nnkOfInherit.newTree( + parentType + ), + fieldsList + ) + ) + )) + + echo "Type:\n", typeDefinition.repr + + # Next: generate initializer with prefilled kind + # Be vary of how we write the kind. SHould it be normalized or exactly like the user wrote it? + # It's more predictable if it is like the user wrote it. But more reasonable to normalize it... + + var procParams = @[capitalizedTypeName.ident] & toSeq(fieldsList.children) + var objectConstructor = nnkObjConstr.newTree( + capitalizedTypeName.ident + ) + objectConstructor.add newColonExpr(ident"kind", capitalizedTypeName.newLit) + for (fName, _) in fields: + objectConstructor.add newColonExpr(fName.ident, fName.ident) + var procBody = newStmtList(objectConstructor) + + var procName = postfix(ident("new" & capitalizedTypeName), "*") + let initializer = newProc(procName, procParams, procBody) + + echo "init:\n", initializer.repr + + # Next: generate nbImageToHtml from toHtmlBody + let renderProcName = (lowercasedTypeName & "ToHtml").ident + let renderProc = genAst(name = renderProcName, body = toHtmlBody, blk = ident"blk", nb = ident"nb", typeName=capitalizedTypeName.ident): + proc name*(blk: NbBlock, nb: Nb): string = + let blk = typeName(blk) + body + + + # Next: generate these lines: + # nbToHtml.funcs["NbImage"] = nbImageToHtml + # addNbBlockToJson(NbImage) + # should we make this into a proc instead so it can be used in the non-sugar variant as well? + let hookAssignements = genAst(key = capitalizedTypeName.newLit, f = renderProcName, typeName = capitalizedTypeName.ident): + nbToHtml.funcs[key] = f + addNbBlockToJson(typeName) + + result = newStmtList( + typeDefinition, + initializer, + renderProc, + hookAssignements + ) + + echo result.repr + + #assert false + +macro withNewlines*(body: untyped) : string = + body.expectKind nnkStmtList + result = infix(body[0], "&", "\n".newLit) + if body.len > 1: + for line in body[1..^1]: + # TODO: handle for loops + # if it's an if-statement, check if it has an else-clause. Otherwise add one which returns "" + if line.kind == nnkIfStmt and line.findChild(it.kind == nnkElse).isNil: + line.add nnkElse.newTree("".newLit) + result = infix(result, "&", line) + result = infix(result, "&", "\n".newLit) + #echo result.treerepr + #echo result.repr + + + diff --git a/src/nimib/renders.nim b/src/nimib/renders.nim index cbcd9b7..10337dd 100644 --- a/src/nimib/renders.nim +++ b/src/nimib/renders.nim @@ -1,18 +1,24 @@ import std / [strutils, tables, sugar, os, strformat, sequtils] -import ./types, ./jsutils, markdown, mustache +import ./types, markdown, mustache # ./jsutils, import highlight import mustachepkg/values -proc mdOutputToHtml(doc: var NbDoc, blk: var NbBlock) = +#[ proc mdOutputToHtml(doc: var NbDoc, blk: var NbBlock) = blk.context["outputToHtml"] = markdown(blk.output, config=initGfmConfig()).dup(removeSuffix) proc highlightCode(doc: var NbDoc, blk: var NbBlock) = - blk.context["codeHighlighted"] = highlightNim(blk.code) + blk.context["codeHighlighted"] = highlightNim(blk.code) ]# +func nbContainerToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbContainer + for b in blk.blocks: + result.add nb.render(b).strip & '\n' + result.strip proc useHtmlBackend*(doc: var NbDoc) = - doc.partials["nbText"] = "{{&outputToHtml}}" + discard +#[ doc.partials["nbText"] = "{{&outputToHtml}}" doc.partials["nbCode"] = """ {{>nbCodeSource}} {{>nbCodeOutput}}""" @@ -54,9 +60,9 @@ proc useHtmlBackend*(doc: var NbDoc) = doc.renderProcs = initTable[string, NbRenderProc]() doc.renderProcs["mdOutputToHtml"] = mdOutputToHtml doc.renderProcs["highlightCode"] = highlightCode - doc.renderProcs["compileNimToJs"] = compileNimToJs + doc.renderProcs["compileNimToJs"] = compileNimToJs ]# -proc useMdBackend*(doc: var NbDoc) = +#[ proc useMdBackend*(doc: var NbDoc) = doc.partials["document"] = """ {{#blocks}} @@ -132,3 +138,4 @@ proc render*(nb: var NbDoc): string = blocks.add nb.render(blk) nb.context["blocks"] = blocks return "{{> document}}".render(nb.context) + ]# \ No newline at end of file diff --git a/src/nimib/themes.nim b/src/nimib/themes.nim index 94db567..2a6b7b4 100644 --- a/src/nimib/themes.nim +++ b/src/nimib/themes.nim @@ -1,6 +1,109 @@ -from mustachepkg/values import castStr -import types, gits, highlight, config +#from mustachepkg/values import castStr +import std/[json, macros, sequtils, strutils, strformat] +import types, gits, highlight, config, jsons, globals, nimibSugars + +func nbContainerToHtml(blk: NbBlock, nb: Nb): string = + let blk = blk.NbContainer + for b in blk.blocks.items: + result.add nb.render(b).strip & '\n' + result.strip +# should I add this to the global object? + +# github light svg adapted from: https://iconify.design/icon-sets/octicon/mark-github.html +# github dark svg taken directly from github website +const githubLogoLight* = """<svg aria-hidden="true" width="1.2em" height="1.2em" style="vertical-align: middle;" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59c.4.07.55-.17.55-.38c0-.19-.01-.82-.01-1.49c-2.01.37-2.53-.49-2.69-.94c-.09-.23-.48-.94-.82-1.13c-.28-.15-.68-.52-.01-.53c.63-.01 1.08.58 1.23.82c.72 1.21 1.87.87 2.33.66c.07-.52.28-.87.51-1.07c-1.78-.2-3.64-.89-3.64-3.95c0-.87.31-1.59.82-2.15c-.08-.2-.36-1.02.08-2.12c0 0 .67-.21 2.2.82c.64-.18 1.32-.27 2-.27c.68 0 1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82c.44 1.1.16 1.92.08 2.12c.51.56.82 1.27.82 2.15c0 3.07-1.87 3.75-3.65 3.95c.29.25.54.73.54 1.48c0 1.07-.01 1.93-.01 2.2c0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z" fill="#000"></path></svg>""" +const githubLogoDark* = """<svg aria-hidden="true" width="1.2em" height="1.2em" style="vertical-align: middle; fill: #fff" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>""" + +func getTitle*(doc: NbDoc): string = + doc.context{"title"}.getStr("nimib document") + + +# All of this should be moved to renders.nim? +func headToHtml*(doc: NbDoc): string = + result = withNewlines: + fmt""" +<head> + <title>{doc.getTitle} + + + + {doc.context.getOrDefault("stylesheet").getStr} + {doc.context.getOrDefault("highlight").getStr} + {doc.context.getOrDefault("nb_style").getStr} + {doc.context.getOrDefault("latex").getStr} + """ + if not doc.context{"disableHighlightJs"}.getBool(false): + doc.context{"highlightJs"}.getStr + "" + + + + +func madeWithNimibToHtml*(): string = + """made with nimib 🐳""" + +func homeLinkToHtml*(homePath: string): string = + fmt"""🏡""" + +func headerLeftToHtml*(doc: NbDoc): string = + homeLinkToHtml(doc.context{"path_to_root"}.getStr("/")) + +func headerCenterToHtml*(doc: NbDoc): string = + let title = doc.getTitle() + fmt"{title}" + +func headerRightToHtml*(doc: NbDoc): string = + if not isNil(doc.context{"github_remote_url"}): + let githubRemoteUrl = doc.context{"github_remote_url"}.getStr + let githubLogo = doc.context{"github_logo"}.getStr(githubLogoLight) + result = fmt"""{github_logo}""" + +func headerToHtml*(doc: NbDoc): string = + result = withNewlines: + "
" + """
""" + " " & headerLeftToHtml(doc) & "" + " " & headerCenterToHtml(doc) & "" + " " & headerRightToHtml(doc) & "" + "
" + "
" + "
" + +func leftToHtml*(): string = + "" + +func mainToHtml*(blk: NbBlock, nb: Nb): string = + result = withNewlines: + "
" + nbContainerToHtml(blk, nb) + "
" + +func rightToHtml*(): string = + "" + +func footerToHtml*(): string = + "" + +func nbDocToHtml*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbDoc + result = withNewlines: + "" + """""" + headToHtml(blk) + "" + headerToHtml(blk) + leftToHtml() + mainToHtml(blk, nb) + rightToHtml() + footerToHtml() + "" + # TODO: Convert all constants below into functions with inputs! + +addNbBlockToJson(NbDoc) # should this be here? Probably not! +nbToHtml.funcs["NbDoc"] = nbDocToHtml + +# TODO: Make these old partials into procs with inputs so they can be reused! const document* = """ @@ -97,10 +200,6 @@ const header* = """ """ const homeLink* = """🏡""" const githubLink* = """{{{github_logo}}}""" -# github light svg adapted from: https://iconify.design/icon-sets/octicon/mark-github.html -# github dark svg taken directly from github website -const githubLogoLight* = """""" -const githubLogoDark* = """""" const footer* = """