diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cac2e8d1..311058b7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v2 - name: install nim id: install_nim - uses: iffy/install-nim@v3 + uses: iffy/install-nim@v5 - name: install library dependencies run: nimble install -y - name: install doc dependencies diff --git a/.github/workflows/netlify_build_docs.yml b/.github/workflows/netlify_build_docs.yml index 63be024c..b1d4320a 100644 --- a/.github/workflows/netlify_build_docs.yml +++ b/.github/workflows/netlify_build_docs.yml @@ -8,10 +8,12 @@ jobs: tests: runs-on: ubuntu-latest steps: + - name: apt install + run: sudo apt install -y libpcre3 libblas-dev liblapack-dev - uses: actions/checkout@v2 - name: install nim id: install_nim - uses: iffy/install-nim@v3 + uses: iffy/install-nim@v5 - name: install library dependencies run: nimble install -y - name: install doc dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d80f86c2..b3663764 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,16 +10,16 @@ jobs: strategy: matrix: nim: - - '1.6.x' - 'stable' - 'devel' fail-fast: false name: Nim ${{ matrix.nim }} steps: - uses: actions/checkout@v3 - - uses: jiro4989/setup-nim-action@v1.4.3 + - uses: jiro4989/setup-nim-action@v2.2.2 with: nim-version: ${{ matrix.nim }} + - run: sudo apt install libpcre3 libpcre3-dev - run: nimble -y install - run: nimble docsdeps - run: nimble test diff --git a/README.md b/README.md index ab4584f8..72bb557b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # nimib 🐳 - nim 👑 driven ⛵ publishing ✍ Nimib provides an API to convert your Nim code and its outputs to html documents. @@ -50,8 +49,6 @@ provides syntax highlighting of embedded languages in nimib documents (eg. markd First have a look at the following html document: [hello.html](https://pietroppeter.github.io/nimib/hello.html) This was produced with `nim r docsrc/hello`, where [docsrc/hello.nim](https://github.com/pietroppeter/nimib/blob/main/docsrc/hello.nim) is: - - ```nim import strformat, strutils import nimib @@ -100,8 +97,6 @@ nbText: nbSave ``` - - ### Other examples of usage in this repo: @@ -228,16 +223,11 @@ that allow to override the above value, skip the config file or other options. All the options available can be seen by running any nimib file with option `nbHelp` (execution will stop after `nbInit`). - - - ```nim import osproc -withDir nb.srcDir: +withDir nb.doc.srcDir: echo execProcess("nim r --verbosity:0 --hints:off --warnings:off hello --nbHelp") ``` - - ``` Nimib options: @@ -248,12 +238,9 @@ Nimib options: --nbHomeDir, --nimibHomeDir set homeDir as relative (to CfgDir) or absolute; overrides config --nbFilename, --nimibFilename overrides name of output file (e.g. somefile --nbFilename:othername.html) --nbShow, --nimibShow open in browser at the end of nbSave -``` - - - +``` The value of options are available in `nb.options` field which also tracks further options in `nb.options.other: seq[tuple[kind: CmdLineKind; name, value: string]]`. @@ -371,5 +358,4 @@ because I made a [package](https://github.com/pietroppeter/nimoji) for that and ### why the Q & A? because [someone made it into an art form](https://github.com/oakes/vim_cubed#q--a) -and they tell me [imitation is the sincerest form of flattery](https://www.goodreads.com/quotes/558084-imitation-is-the-sincerest-form-of-flattery-that-mediocrity-can) - +and they tell me [imitation is the sincerest form of flattery](https://www.goodreads.com/quotes/558084-imitation-is-the-sincerest-form-of-flattery-that-mediocrity-can) \ No newline at end of file diff --git a/docsrc/allblocks.nim b/docsrc/allblocks.nim index 460cb8de..a82a3a4c 100644 --- a/docsrc/allblocks.nim +++ b/docsrc/allblocks.nim @@ -4,17 +4,17 @@ import std/[strutils, strformat] nbInit -var nbToc: NbBlock +var nbToc: NbText template addToc = - newNbBlock("nbText", false, nb, nbToc, ""): - nbToc.output = "## Table of Contents:\n\n" + nbToc = newNbText(text="## Table of Contents:\n\n") + nb.add nbToc -template nbCodeBlock(name:string) = +template nbCodeBlock(name: string) = let anchorName = name.toLower.replace(" ", "-") nbText "\n### " & name & "\n\n---" # see below, but any number works for a numbered list - nbToc.output.add "1. " & name & "\n" + nbToc.text.add "1. " & name & "\n" nbText: """ > This nimib document provides a brief description and example for @@ -120,7 +120,7 @@ The `typ` parameter specifies the video's MIME type, and is optional! """ nimibCode: - nbVideo(url="media/bad_apple!!.mp4", typ="video/mp4") + nbVideo(url="media/bad_apple!!.mp4", filetype="video/mp4") nbCodeBlock: "nbAudio" nbText: """ diff --git a/docsrc/cheatsheet.nim b/docsrc/cheatsheet.nim index 3f10f990..ddb1fdb3 100644 --- a/docsrc/cheatsheet.nim +++ b/docsrc/cheatsheet.nim @@ -1,5 +1,5 @@ ## Markdown cheatsheet with nimib -import nimib, std/strutils +import nimib, std/[strutils, strformat, json] nbInit nbText: """ @@ -15,34 +15,36 @@ nbText: """ """ # customize source highlighting: -nb.context["highlight"] = """ +nb.doc.context["highlight"] = %""" """ + # a custom text block that shows markdown source -template nbTextWithSource*(body: untyped) = - newNbBlock("nbTextWithSource", false, nb, nb.blk, body): - nb.blk.output = body - nb.blk.context["code"] = body +newNbBlock(NbTextWithSource of NbText): + toHtml: + result = withNewlines: + nbTextToHtml(blk, nb) + &"""
{blk.text}
""" -nb.renderPlans["nbTextWithSource"] = @["mdOutputToHtml"] -nb.partials["nbTextWithSource"] = """{{&outputToHtml}} -
{{code}}
""" +template nbTextWithSource*(body: string) = + let blk = newNbTextWithSource(text=body) + nb.add blk # how to add a ToC var - nbToc: NbBlock + nbToc: NbText template addToc = - newNbBlock("nbText", false, nb, nbToc, ""): - nbToc.output = "# Table of Contents:\n\n" + nbToc = newNbText(text="# Table of Contents:\n\n") + nb.add nbToc template nbSection(name:string) = let anchorName = name.toLower.replace(" ", "-") nbText "\n# " & name & "\n\n---" # see below, but any number works for a numbered list - nbToc.output.add "1. " & name & "\n" + nbToc.text.add "1. " & name & "\n" # here is the start of the document nbText """ @@ -100,7 +102,7 @@ nbTextWithSource """1. First ordered list item * Unordered list can use asterisks - Or minuses + Or pluses""" -nb.blk.output = nb.blk.output.replace(dot, " ") +nb.blk.NbTextWithSource.text = nb.blk.NbTextWithSource.text.replace(dot, " ") nbText """ > in ordered to have the ordered sublist work correctly diff --git a/docsrc/far/from/home.nim b/docsrc/far/from/home.nim index 71b219c0..f3187b90 100644 --- a/docsrc/far/from/home.nim +++ b/docsrc/far/from/home.nim @@ -1,7 +1,7 @@ import nimib, strutils nbInit -nb.context["path_to_root"] = nb.context["path_to_root"].castStr.replace("/", r"\") # for CI since I run locally on Windows and CI runs on Linux: behaviour is the same -nb.context["path_to_here"] = nb.context["path_to_here"].castStr.replace("/", r"\") # for CI since I run locally on Windows and CI runs on Linux: behaviour is the same +nb.doc.context["path_to_root"] = %nb.doc.context{"path_to_root"}.getStr.replace("/", r"\") # for CI since I run locally on Windows and CI runs on Linux: behaviour is the same +nb.doc.context["path_to_here"] = %nb.doc.context{"path_to_here"}.getStr.replace("/", r"\") # for CI since I run locally on Windows and CI runs on Linux: behaviour is the same nb.partials["document"] = """ diff --git a/docsrc/index.nim b/docsrc/index.nim index b9a4b6d0..cf2b37b1 100644 --- a/docsrc/index.nim +++ b/docsrc/index.nim @@ -12,7 +12,7 @@ nb.title = "Nimib Docs" let repo = "https://github.com/pietroppeter/nimib" docs = if defined(mdOutput): "https://pietroppeter.github.io/nimib" else: "." - hello = read(nb.srcDir / "hello.nim".RelativeFile) + hello = read(nb.doc.srcDir / "hello.nim".RelativeFile) nbText: hlMdF""" # nimib :whale: - nim :crown: driven :sailboat: publishing :writingHand: @@ -65,13 +65,13 @@ provides syntax highlighting of embedded languages in nimib documents (eg. markd First have a look at the following html document: [hello.html]({docs}/hello.html) -This was produced with `nim r {nb.cfg.srcDir}/hello`, where [{nb.cfg.srcDir}/hello.nim]({repo}/blob/main/{nb.cfg.srcDir}/hello.nim) is: +This was produced with `nim r {nb.doc.cfg.srcDir}/hello`, where [{nb.doc.cfg.srcDir}/hello.nim]({repo}/blob/main/{nb.doc.cfg.srcDir}/hello.nim) is: """.emojize when not defined(mdOutput): nbCode: discard - nb.blk.code = hello # "\n" should not be needed here (fix required in rendering) + nb.blk.NbCode.code = hello # "\n" should not be needed here (fix required in rendering) else: nbText &""" ```nim @@ -124,7 +124,7 @@ The following are the main elements of a default nimib document: * (optional) latex rendering through [katex](https://katex.org/) (see below) * a header with navigation to a home page, a minimal title and an automatic detection of github repo (with link) * a footer with a "made with nimib" line and a `Show source` button that shows the full source to create the document. -* (optional) possibility to create a markdown version of the same document (see this document for an example: [{nb.cfg.srcDir}/index.nim]({repo}/blob/main/{nb.cfg.srcDir}/index.nim)) +* (optional) possibility to create a markdown version of the same document (see this document for an example: [{nb.doc.cfg.srcDir}/index.nim]({repo}/blob/main/{nb.doc.cfg.srcDir}/index.nim)) Customization over the default is mostly achieved through nim-mustache or changing `NbDoc` and `NbBlock` elements (see below api). @@ -210,7 +210,7 @@ All the options available can be seen by running any nimib file with option `nbH nbCode: import osproc - withDir nb.srcDir: + withDir nb.doc.srcDir: echo execProcess("nim r --verbosity:0 --hints:off --warnings:off hello --nbHelp") let renderProcType = "type NbRenderProc = proc (doc: var NbDoc, blk: var NbBlock) {. nimcall .}" @@ -338,7 +338,7 @@ and they tell me [imitation is the sincerest form of flattery](https://www.goodr """.emojize when defined(mdOutput): - nb.filename = "../README.md" + nb.doc.filename = "../README.md" nbSave else: nbSave diff --git a/docsrc/nolan.nim b/docsrc/nolan.nim index 4c74069b..0226cb9b 100644 --- a/docsrc/nolan.nim +++ b/docsrc/nolan.nim @@ -1,5 +1,5 @@ import nimib -import strutils, sequtils, strformat +import strutils, sequtils, strformat, json nbInit nbText: """ @@ -11,7 +11,7 @@ nbText: """ > > It reorders a **Limerick** according to Nolan's [Memento film structure](https://en.wikipedia.org/wiki/Memento_(film)#Film_structure). """ -let filename_variant = nb.filename.replace(".html", "_no_source.html") +let filename_variant = nb.doc.filename.replace(".html", "_no_source.html") let iChange = nb.blocks.len nbText:fmt""" > Click on `Show Source` at the bottom to see the nim file that generates this document. @@ -48,11 +48,11 @@ nbSave # save another document without source to test the opt-out of Show Source feature nbText: "---\n> This is how we can remove the `Show Source` functionality" nbCode: - nb.context["no_source"] = true + nb.doc.context["no_source"] = %true # we will generate this last block to change a previous block and then remove it nbText:fmt""" -> To see the variant with Show Source [click here]({(nb.filename.AbsoluteFile).relPath}) +> To see the variant with Show Source [click here]({(nb.doc.filename.AbsoluteFile).relPath}) """ -nb.blocks[iChange] = nbDoc.blocks.pop -nb.filename = filename_variant +nb.blocks[iChange] = nb.blocks.pop +nb.doc.filename = filename_variant nbSave \ No newline at end of file diff --git a/docsrc/numerical.nim b/docsrc/numerical.nim index 0c4b310d..5708efd7 100644 --- a/docsrc/numerical.nim +++ b/docsrc/numerical.nim @@ -1,16 +1,16 @@ #remember to run also with -d:numericalDefaultStyle -import nimib, strformat, strutils +import nimib, strformat, strutils, json nbInit nb.useLatex let - filename_default_style = nb.filename.replace(".html", "_default_style.html") - default_style = nb.context["stylesheet"] + filename_default_style = nb.doc.filename.replace(".html", "_default_style.html") + default_style = nb.doc.context["stylesheet"] -nb.context["stylesheet"] = """""" +nb.doc.context["stylesheet"] = %"""""" proc introText(defaultStyle = false): string = let otherStyle = if defaultStyle: - fmt"; _you are looking at default style, for custom style [click here]({(nb.filename.AbsoluteFile).relPath})_" + fmt"; _you are looking at default style, for custom style [click here]({(nb.doc.filename.AbsoluteFile).relPath})_" else: fmt"; _for default style [click here]({(filename_default_style.AbsoluteFile).relPath})_" @@ -26,7 +26,7 @@ proc introText(defaultStyle = false): string = nbText: introText() # save to change later -var blockIntroText = nb.blk +var blockIntroText = nb.blk.NbText nbText: fmt"""# Using NumericalNim @@ -128,17 +128,17 @@ $h=0.01$ | {pe y3hn[^1]} | {pe y3rk[^1]} and here is the code for this Markdown block: """ # right alignment of numbers does not seem to work. is this an issue of latex.css? -let mdCode = nb.blk.code +let mdCode = nb.blk.NbTextWithCode.code nbCode: discard -nb.blk.code = mdCode +nb.blk.NbCode.code = mdCode # add a show Markdown source. It would be nice when hovering a code block to show (on the side? how to do it on mobile?) the call code (nbText: or other) # first save with latex style nbSave # then save with default style -blockIntroText.output = introText(default_style=true) -nb.filename = filename_default_style -nb.context["stylesheet"] = default_style +blockIntroText.text = introText(default_style=true) +nb.doc.filename = filename_default_style +nb.doc.context["stylesheet"] = default_style nbSave diff --git a/docsrc/pythno.nim b/docsrc/pythno.nim index ac4fd6d9..27e946dd 100644 --- a/docsrc/pythno.nim +++ b/docsrc/pythno.nim @@ -25,8 +25,9 @@ nbCode: echo repeat("ThisIsNotPython", 6) -nb.blk.context["output"] = camel2snake(nb.blk.output) -nb.blk.code = nb.blk.code.multiReplace([ +let codeBlock = nb.blk.NbCode +codeBlock.output = camel2snake(codeBlock.output) +codeBlock.code = codeBlock.code.multiReplace([ ("proc", "def"), ("string", "str"), (";", ","), # tricky one diff --git a/src/nimib.nim b/src/nimib.nim index 372d2457..7054618d 100644 --- a/src/nimib.nim +++ b/src/nimib.nim @@ -1,67 +1,64 @@ -import std/[os, strutils, sugar, strformat, macros, macrocache, sequtils, jsonutils] -export jsonutils -import nimib / [types, blocks, docs, boost, config, options, capture, jsutils, logging] -export types, blocks, docs, boost, sugar, jsutils +import std/[os, strutils, sugar, strformat, macros, macrocache, sequtils, json] +import std / jsonutils except toJson +export jsonutils except toJson +import markdown +import nimib / [types, blocks, docs, boost, config, options, capture, jsons, globals, jsutils, nimibSugars, sources, highlight, logging, renders, builtinBlocks] +export types, blocks, docs, boost, sugar, globals, nimibSugars, jsutils, sources, highlight, jsons, renders, builtinBlocks # types exports mustache, tables, paths from nimib / themes import nil export themes.useLatex, themes.darkMode, themes.`title=`, themes.disableHighlightJs -from nimib / renders import nil - from mustachepkg/values import searchTable, searchDirs, castStr 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: proc (nb: var Nb) = 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 - log "thisFile: " & $nb.thisFile + nb.doc.thisFile = nb.doc.srcDir / thisFileRel.RelativeFile + log "thisFile: " & $nb.doc.thisFile try: - nb.source = read(nb.thisFile) + nb.doc.source = read(nb.doc.thisFile) except IOError: log "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 != "": - log "srcDir: " & $nb.srcDir - nb.filename = (nb.thisDir.relativeTo nb.srcDir).string / nb.filename - log "filename: " & nb.filename + if nb.doc.cfg.srcDir != "": + log "srcDir: " & $nb.doc.srcDir + nb.doc.filename = (nb.doc.thisDir.relativeTo nb.doc.srcDir).string / nb.doc.filename + log "filename: " & nb.doc.filename - if nb.cfg.homeDir != "": - if not dirExists(nb.homeDir): - log "creating nb.homeDir: " & $nb.homeDir - createDir(nb.homeDir) - - log "setting current directory to nb.homeDir: " & $nb.homeDir + if nb.doc.cfg.homeDir != "": + if not dirExists(nb.doc.homeDir): + log "creating nb.homeDir: " & $nb.doc.homeDir + createDir(nb.doc.homeDir) - setCurrentDir nb.homeDir + log "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 # how do we handle themes? template nbInitMd*(thisFileRel = "") = var tfr = if thisFileRel == "": @@ -69,216 +66,20 @@ template nbInitMd*(thisFileRel = "") = else: thisFileRel - nbInit(backend=renders.useMdBackend, theme=themes.noTheme, tfr) + nbInit(renderer=nbToMd, theme=themes.noTheme, tfr) - if nb.options.filename == "": - nb.filename = nb.filename.splitFile.name & ".md" + if nb.doc.options.filename == "": + nb.doc.filename = nb.doc.filename.splitFile.name & ".md" -# block generation templates +#[ # block generation templates template newNbCodeBlock*(cmd: string, body, blockImpl: untyped) = newNbBlock(cmd, true, nb, nb.blk, body, blockImpl) template newNbSlimBlock*(cmd: string, blockImpl: untyped) = # a slim block is a block with no body - newNbBlock(cmd, false, nb, nb.blk, "", blockImpl) - -# block templates -template nbCode*(body: untyped) = - newNbCodeBlock("nbCode", body): - captureStdout(nb.blk.output): - body - -template nbCodeSkip*(body: untyped) = - newNbCodeBlock("nbCodeSkip", body): - discard - -template nbCapture*(body: untyped) = - newNbCodeBlock("nbCapture", body): - captureStdout(nb.blk.output): - body - -template nbCodeInBlock*(body: untyped): untyped = - block: - nbCode: - body - -template nimibCode*(body: untyped) = - newNbCodeBlock("nimibCode", body): - discard - body - -template nbText*(text: string) = - newNbSlimBlock("nbText"): - nb.blk.output = text - -template nbTextWithCode*(body: untyped) = - newNbCodeBlock("nbText", body): - nb.blk.output = body - -template nbImage*(url: string, caption = "", alt = "") = - newNbSlimBlock("nbImage"): - nb.blk.context["url"] = nb.relToRoot(url) - nb.blk.context["alt_text"] = - if alt == "": - caption - else: - alt - - nb.blk.context["caption"] = caption - -# todo captions and subtitles support maybe? -template nbVideo*(url: string, typ: string = "", autoplay = false, muted = false, loop: bool = false) = - newNbSlimBlock("nbVideo"): - nb.blk.context["url"] = nb.relToRoot(url) - nb.blk.context["type"] = - if typ == "": "video/" & url.splitFile.ext[1..^1] # remove the leading dot - else: typ - - if autoplay: nb.blk.context["autoplay"] = "autoplay" - if muted: nb.blk.context["muted"] = "muted" - if loop: nb.blk.context["loop"] = "loop" - -template nbAudio*(url: string, typ: string = "", autoplay = false, muted = false, loop: bool = false) = - newNbSlimBlock("nbAudio"): - nb.blk.context["url"] = nb.relToRoot(url) - nb.blk.context["type"] = - if typ == "": "audio/" & url.splitFile.ext[1..^1] - else: typ - - if autoplay: nb.blk.context["autoplay"] = "autoplay" - if muted: nb.blk.context["muted"] = "muted" - if loop: nb.blk.context["loop"] = "loop" - -template nbFile*(name: string, content: string) = - ## Generic string file - newNbSlimBlock("nbFile"): - name.writeFile content - nb.blk.context["filename"] = name - nb.blk.context["ext"] = name.getExt - nb.blk.context["content"] = content - -template nbFile*(name: string, body: untyped) = - newNbCodeBlock("nbFile", body): - name.writeFile nb.blk.code - nb.blk.context["filename"] = name - nb.blk.context["ext"] = name.getExt - nb.blk.context["content"] = nb.blk.code - -template nbFile*(name: string) = - ## Read content from a file instead of writing to it - newNbSlimBlock("nbFile"): - nb.blk.context["filename"] = name - nb.blk.context["ext"] = name.getExt - nb.blk.context["content"] = readFile(name) - -when moduleAvailable(nimpy): - template nbInitPython*() = - import nimpy - let nbPythonBuiltins = pyBuiltinsModule() - - template nbPython(pythonStr: string) = - newNbSlimBlock("nbPython"): - nb.blk.code = pythonStr - captureStdout(nb.blk.output): - discard nbPythonBuiltins.exec(pythonStr) - -template nbShow*(obj: untyped) = - nbRawHtml(obj.toHtml()) - -template nbRawOutput*(content: string) {.deprecated: "Use nbRawHtml instead".} = - nbRawHtml(content) - -template nbRawHtml*(content: string) = - newNbSlimBlock("nbRawHtml"): - nb.blk.output = content - -template nbJsFromStringInit*(body: string): NbBlock = - var result = NbBlock(command: "nbJsFromCode", code: body, context: newContext(searchDirs = @[], partials = nb.partials), output: "") - result.context["transformedCode"] = body - result.context["putAtTop"] = false - result - -template addStringToJs*(script: NbBlock, body: string) = - script.code &= "\n" & body - script.context["transformedCode"] = script.context["transformedCode"].vString & "\n" & body - -template addToDocAsJs*(script: NbBlock) = - nb.blocks.add script - nb.blk = script - -template nbJsFromString*(body: string) = - let script = nbJsFromStringInit(body) - script.addToDocAsJs - -template nbJsFromCode*(args: varargs[untyped]) = - let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) - var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") - result.context["transformedCode"] = code - result.context["putAtTop"] = false - result.addToDocAsJs - -template nbJsFromCodeInBlock*(args: varargs[untyped]) = - let (code, originalCode) = nimToJsString(putCodeInBlock=true, args) - var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") - result.context["transformedCode"] = code - result.context["putAtTop"] = false - result.addToDocAsJs - -template nbJsFromCodeGlobal*(args: varargs[untyped]) = - let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) - var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") - result.context["transformedCode"] = code - result.context["putAtTop"] = true - result.addToDocAsJs - -template nbJsFromCodeOwnFile*(args: varargs[untyped]) = - let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) - var result = NbBlock(command: "nbJsFromCodeOwnFile", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") - result.context["transformedCode"] = code - result.addToDocAsJs - -template nbCodeToJs*(args: varargs[untyped]) {.deprecated: "Use nbJsFromCode or nbJsFromString instead".} = - nbJsFromCode(args) - - -when moduleAvailable(karax/kbase): - template nbKaraxCode*(args: varargs[untyped]) = - let rootId = "karax-" & $nb.newId() - nbRawHtml: "
" - nbKaraxCodeBackend(rootId, args) - -when moduleAvailable(happyx): - template nbHappyxCode*(args: varargs[untyped]) = - let rootId = "happyx-" & $nb.newId() - nbRawHtml: "
" - nbHappyxCodeBackend(rootId, args) - -template nbJsShowSource*(message: string = "") {.deprecated: "Use nbCodeDisplay instead".} = - nb.blk.context["js_show_nim_source"] = true - if message.len > 0: - nb.blk.context["js_show_nim_source_message"] = message - -template nbCodeToJsShowSource*(message: string = "") {.deprecated: "Use nbCodeDisplay instead".} = - nbJsShowSource(message) - -template nbCodeDisplay*(tmplCall: untyped, body: untyped) = - ## display codes used in a template (e.g. nbJsFromCode) after the template call - tmplCall: - body - newNbCodeBlock("nbCode", body): - discard + newNbBlock(cmd, false, nb, nb.blk, "", blockImpl) ]# -template nbCodeAnd*(tmplCall: untyped, body: untyped) = - ## can be used to run code both in c and js backends (e.g. nbCodeAnd(nbJsFromCode)) - nbCode: # this should work because template name starts with nbCode - body - tmplCall: - body -template nbClearOutput*() = - if not nb.blk.isNil: - nb.blk.output = "" - nb.blk.context["output"] = "" template nbSave* = # order if searchDirs/searchTable is relevant: directories have higher priority. rationale: @@ -287,21 +88,21 @@ 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) + #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 + (path.relativeTo nb.doc.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 ecbc662b..f7a05533 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 # should this be here? + 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) = nbBlock = NbBlock(command: cmd, context: newContext(searchDirs = @[], partials = nbDoc.partials)) when readCode: nbBlock.code = nbNormalize: diff --git a/src/nimib/builtinBlocks.nim b/src/nimib/builtinBlocks.nim new file mode 100644 index 00000000..1f2e1fd6 --- /dev/null +++ b/src/nimib/builtinBlocks.nim @@ -0,0 +1,525 @@ +import std / [strformat, os, strutils, json] +import std / jsonutils except toJson + +import ./types, ./blocks, ./capture, ./nimibSugars, ./globals, ./jsons, ./paths, ./docs, ./jsutils + +template moduleAvailable*(module: untyped): bool = + (compiles do: import module) + +# block templates +#[ template nbCode*(body: untyped) = + newNbCodeBlock("nbCode", body): + captureStdout(nb.blk.output): + body ]# + +newNbBlock(NbCode of NbContainer): + code: string + output: string + toHtml: + withNewlines: + nb.renderPartial("nbCodeSource", jsonutils.toJson(blk)) + nbContainerToHtml(blk, nb) + nb.renderPartial("nbCodeOutput", jsonutils.toJson(blk)) + +proc nbCodeToMd(blk: NbBlock, nb: Nb): string = + let blk = blk.NbCode + withNewlines: + if blk.code.len > 0: + withNewLines: + "```nim" + blk.code + "```" + if blk.output.len > 0: + withNewLines: + "```" + blk.output + "```" + +nbToMd.funcs["NbCode"] = nbCodeToMd + + +template code*(nb: Nb, body: untyped) = + let blk = newNbCode() + blk.code = getCode(body) + nb.withContainer(blk): + captureStdout(blk.output): + body + nb.add blk + +template nbCode*(body: untyped) = + nb.code: + body + +template codeSkip*(nb: Nb, body: untyped) = + let blk = newNbCode() + blk.code = getCode(body) + nb.add blk + +template nbCodeSkip*(body: untyped) = + nb.codeSkip(body) + +#[ template nbCodeSkip*(body: untyped) = + newNbCodeBlock("nbCodeSkip", body): + discard ]# + +template capture*(nb: Nb, body: untyped) = + let blk = newNbCode() + nb.withContainer(blk): + captureStdout(blk.output): + body + nb.add blk + +template nbCapture*(body: untyped) = + nb.capture(body) + +#[ template nbCapture*(body: untyped) = + newNbCodeBlock("nbCapture", body): + captureStdout(nb.blk.output): + body ]# + +template codeInBlock*(nb: Nb, body: untyped) = + block: + nb.code: + body + +template nbCodeInBlock*(body: untyped): untyped = + nb.codeInBlock: + body + +template nimibCode*(body: untyped) = + nbCode: + body + +#[ template nimibCode*(body: untyped) = + newNbCodeBlock("nimibCode", body): + discard + body ]# + +#[ template nbText*(text: string) = + newNbSlimBlock("nbText"): + nb.blk.output = text ]# + +newNbBlock(NbText): + text: string + toHtml: + nb.renderPartial("nbText", jsonutils.toJson(blk)) + +func nbTextToMd*(blk: NbBlock, nb: Nb): string = + blk.NbText.text + +nbToMd.funcs["NbText"] = nbTextToMd + +func 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): + nb.blk.output = body ]# + +newNbBlock(NbTextWithCode of NbText): + code: string + toHtml: + nbTextToHtml(blk, nb) + +func nbTextWithCodeToMd*(blk: NbBlock, nb: Nb): string = + blk.NbTextWithCode.text + +nbToMd.funcs["NbTextWithCode"] = nbTextWithCodeToMd + +template textWithCode*(nb: Nb, body: untyped) = + let ttext = body + let tcode = getCode(body) + let blk = newNbTextWithCode(text=ttext, code=tcode) + nb.add blk + +template nbTextWithCode*(body: untyped) = + nb.textWithCode: + body + +newNbBlock(NbImage): + url: string + caption: string + alt: string + toHtml: + &""" +
+ {blk.alt} +
{blk.caption}
+
+""" + +func nbImageToMd*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbImage + withNewLines: + &"![{blk.alt}]({blk.url})" + if blk.caption.len > 0: + withNewLines: + "" + &"**Figure:** {blk.caption}" + +nbToMd.funcs["NbImage"] = nbImageToMd + +func image*(nb: var Nb, turl: string, tcaption = "", talt = "") = + let blk = newNbImage() + blk.url = + if isAbsolute(turl) or turl.startsWith("http"): + turl + else: + nb.doc.context{"path_to_root"}.getStr / turl + blk.alt = if talt.len == 0: tcaption else: talt + blk.caption = tcaption + nb.add blk + +template nbImage*(url: string, caption = "", alt = "") = + nb.image(url, caption, alt) + +#[ template nbImage*(url: string, caption = "", alt = "") = + newNbSlimBlock("nbImage"): + nb.blk.context["url"] = nb.relToRoot(url) + nb.blk.context["alt_text"] = + if alt == "": + caption + else: + alt + + nb.blk.context["caption"] = caption ]# + +newNbBlock(NbFile): + filename: string + ext: string + content: string + toHtml: + &""" +
{blk.filename}
+
{blk.content}
+""" +# Make pre-code with hljs-extensions into a partial! Or a function that calls a partial even? (that constructs the JsNode for us) + +proc file*(nb: var Nb, tname: string, tcontent: string) = + ## Generic string file + tname.writeFile tcontent + let blk = newNbFile(filename=tname, ext=tname.getExt, content=tcontent) + nb.add blk + +template file*(nb: Nb, tname: string, body: untyped) = + ## Read code and write it to file + let content = getCode(body) + tname.writeFile content + let blk = newNbFile(filename=tname, ext=tname.getExt, content=content) + nb.add blk + +proc file*(nb: var Nb, tname: string) = + ## Read content from a file instead of writing to it + let content = readFile(tname) + let blk = newNbFile(filename=tname, ext=tname.getExt, content=content) + nb.add blk + +newNbBlock(NbVideo): + url: string + filetype: string + autoplay: bool + muted: bool + loop: bool + toHtml: + withNewLines: + "" + +func video*(nb: var Nb, url: string, filetype: string = "", autoplay = false, muted = false, loop: bool = false) = + let blk = newNbVideo(autoplay=autoplay, muted=muted, loop=loop) + blk.url = nb.doc.relToRoot(url) + blk.filetype = + if filetype == "": "video/" & url.splitFile.ext[1..^1] # remove the leading dot + else: filetype + nb.add blk + +# todo captions and subtitles support maybe? +template nbVideo*(url: string, filetype: string = "", autoplay = false, muted = false, loop: bool = false) = + nb.video(url, filetype, autoplay, muted, loop) + +newNbBlock(NbAudio of NbVideo): + toHtml: + withNewLines: + "" + +func audio*(nb: var Nb, url: string, filetype: string = "", autoplay = false, muted = false, loop: bool = false) = + let blk = newNbAudio(autoplay=autoplay, muted=muted, loop=loop) + blk.url = nb.doc.relToRoot(url) + blk.filetype = + if filetype == "": "audio/" & url.splitFile.ext[1..^1] # remove the leading dot + else: filetype + nb.add blk + +template nbAudio*(url: string, filetype: string = "", autoplay = false, muted = false, loop: bool = false) = + nb.audio(url, filetype, autoplay, muted, loop) + +template nbFile*(name: string, content: string) = + nb.file(name, content) + +template nbFile*(name: string, body: untyped) = + nb.file(name, body) + +template nbFile*(name: string) = + nb.file(name) + +#[ template nbFile*(name: string, content: string) = + ## Generic string file + newNbSlimBlock("nbFile"): + name.writeFile content + nb.blk.context["filename"] = name + nb.blk.context["ext"] = name.getExt + nb.blk.context["content"] = content + +template nbFile*(name: string, body: untyped) = + newNbCodeBlock("nbFile", body): + name.writeFile nb.blk.code + nb.blk.context["filename"] = name + nb.blk.context["ext"] = name.getExt + nb.blk.context["content"] = nb.blk.code + +template nbFile*(name: string) = + ## Read content from a file instead of writing to it + newNbSlimBlock("nbFile"): + nb.blk.context["filename"] = name + nb.blk.context["ext"] = name.getExt + nb.blk.context["content"] = readFile(name) ]# + +when moduleAvailable(nimpy): + newNbBlock(NbPython of NbCode): + toHtml: + withNewlines: + if blk.code.len > 0: + &"
{blk.code}
" + if blk.output.len > 0: + &"
{blk.output}
" + + template nbInitPython*() = + import nimpy + let nbPythonBuiltins = pyBuiltinsModule() + + proc python(nb: var Nb, pythonStr: string) = + let blk = newNbPython(code = pythonStr) + captureStdout(blk.output): + discard nbPythonBuiltins.exec(pythonStr) + nb.add blk + + template nbPython(pythonStr: string) = + nb.python(pythonStr) + + #[ template nbPython(pythonStr: string) = + newNbSlimBlock("nbPython"): + nb.blk.code = pythonStr + captureStdout(nb.blk.output): + discard nbPythonBuiltins.exec(pythonStr) ]# + +newNbBlock(NbRawHtml): + html: string + toHtml: + blk.html + +func rawHtml*(nb: var Nb, content: string) = + let blk = newNbRawHtml(html = content) + nb.add blk + +template nbRawHtml*(content: string) = + nb.rawHtml(content) + +func show*[T](nb: var Nb, obj: T) = + nb.rawHtml(obj.toHtml()) + +template nbShow*(obj: untyped) = + nb.show(obj) + +newNbBlock(NbDiv of NbContainer): + class: string + style: string + toHtml: + withNewLines: + &"
" + nbContainerToHtml(blk, nb) + "
" + +template nbDiv*(classes: string, styles: string, body: untyped) = + let blk = newNbDiv(class=classes, style=styles) + nb.withContainer(blk): + body + nb.add blk + +template nbDiv*(body: untyped) = + nbDiv("", ""): + body + +#[ template nbShow*(obj: untyped) = + nbRawHtml(obj.toHtml()) + +template nbRawOutput*(content: string) {.deprecated: "Use nbRawHtml instead".} = + nbRawHtml(content) + +template nbRawHtml*(content: string) = + newNbSlimBlock("nbRawHtml"): + nb.blk.output = content ]# + +func nbJsFromStringInit*(body: string): NbBlock = + newNbJsFromCode(code=body, transformedCode=body, putAtTop=false) + +#[ template nbJsFromStringInit*(body: string): NbBlock = + var result = NbBlock(command: "nbJsFromCode", code: body, context: newContext(searchDirs = @[], partials = nb.partials), output: "") + result.context["transformedCode"] = body + result.context["putAtTop"] = false + result ]# + +func addStringToJs*(script: NbJsFromCode or NbJsFromCodeOwnFile, body: string) = + script.code &= "\n" & body + script.transformedCode &= "\n" & body + +#[ template addStringToJs*(script: NbBlock, body: string) = + script.code &= "\n" & body + script.context["transformedCode"] = script.context["transformedCode"].vString & "\n" & body ]# + +#[ func addToDocAsJs*(nb: var Nb, script: NbBlock) = + nb.add script + +template addToDocAsJs*(script: NbBlock) = + nb.blocks.add script + nb.blk = script ]# + +func jsFromString*(nb: var Nb, body: string) = + let script = nbJsFromStringInit(body) + nb.add script + +template nbJsFromString*(body: string) = + nb.jsFromString(body) + +template jsFromCode*(nb: var Nb, args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = newNbJsFromCode(code=originalCode, transformedCode=code, putAtTop=false) + nb.add blk + +template nbJsFromCode*(args: varargs[untyped]) = + nb.jsFromCode(args) + +template jsFromCodeInBlock*(nb: var Nb, args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=true, args) + let blk = newNbJsFromCode(code=originalCode, transformedCode=code, putAtTop=false) + nb.add blk + +template nbJsFromCodeInBlock*(args: varargs[untyped]) = + nb.jsFromCodeInBlock(args) + +template jsFromCodeGlobal*(nb: var Nb, args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = newNbJsFromCode(code=originalCode, transformedCode=code, putAtTop=true) + nb.add blk + +template nbJsFromCodeGlobal*(args: varargs[untyped]) = + nb.jsFromCodeGlobal(args) + +template jsFromCodeOwnFile*(nb: var Nb, args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + let blk = newNbJsFromCodeOwnFile(code=originalCode, transformedCode=code) + nb.add blk + +template nbJsFromCodeOwnFile*(args: varargs[untyped]) = + nb.jsFromCodeOwnFile(args) + +#[ template nbJsFromCode*(args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") + result.context["transformedCode"] = code + result.context["putAtTop"] = false + result.addToDocAsJs + +template nbJsFromCodeInBlock*(args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=true, args) + var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") + result.context["transformedCode"] = code + result.context["putAtTop"] = false + result.addToDocAsJs + +template nbJsFromCodeGlobal*(args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + var result = NbBlock(command: "nbJsFromCode", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") + result.context["transformedCode"] = code + result.context["putAtTop"] = true + result.addToDocAsJs + +template nbJsFromCodeOwnFile*(args: varargs[untyped]) = + let (code, originalCode) = nimToJsString(putCodeInBlock=false, args) + var result = NbBlock(command: "nbJsFromCodeOwnFile", code: originalCode, context: newContext(searchDirs = @[], partials = nb.partials), output: "") + result.context["transformedCode"] = code + result.addToDocAsJs + +template nbCodeToJs*(args: varargs[untyped]) {.deprecated: "Use nbJsFromCode or nbJsFromString instead".} = + nbJsFromCode(args) ]# + + +when moduleAvailable(karax/kbase): + template karaxCode*(nb: var Nb, args: varargs[untyped]) = + let rootId = "karax-" & $nb.doc.newId() + nb.rawHtml: "
" + nbKaraxCodeBackend(rootId, args) + + template nbKaraxCode*(args: varargs[untyped]) = + nb.karaxCode(args) + +when moduleAvailable(happyx): + template happyxCode*(nb: var Nb, args: varargs[untyped]) = + let rootId = "happyx-" & $nb.doc.newId() + nbRawHtml: "
" + nbHappyxCodeBackend(rootId, args) + + template nbHappyxCode*(args: varargs[untyped]) = + nb.happyxCode(args) + +#[ template nbJsShowSource*(message: string = "") {.deprecated: "Use nbCodeDisplay instead".} = + nb.blk.context["js_show_nim_source"] = true + if message.len > 0: + nb.blk.context["js_show_nim_source_message"] = message + +template nbCodeToJsShowSource*(message: string = "") {.deprecated: "Use nbCodeDisplay instead".} = + nbJsShowSource(message) ]# + +template codeDisplay*(nb: var Nb, tmplCall: untyped, body: untyped) = + tmplCall: + body + nb.codeSkip: + body + +template nbCodeDisplay*(tmplCall: untyped, body: untyped) = + ## display codes used in a template (e.g. nbJsFromCode) after the template call + nb.codeDisplay(tmplCall, body) + +template codeAnd*(nb: var Nb, tmplCall: untyped, body: untyped) = + ## can be used to run code both in c and js backends (e.g. nbCodeAnd(nbJsFromCode)) + nb.code: # this should work because template name starts with nbCode + body + tmplCall: + body + +template nbCodeAnd*(tmplCall: untyped, body: untyped) = + nb.codeAnd(tmplCall, body) + +template nbClearOutput*() = + if not nb.blk.isNil and nb.blk of NbCode: + nb.blk.NbCode.output = "" \ No newline at end of file diff --git a/src/nimib/docs.nim b/src/nimib/docs.nim index 4840a17c..6b8d05e5 100644 --- a/src/nimib/docs.nim +++ b/src/nimib/docs.nim @@ -1,4 +1,4 @@ -import std/os +import std/[os, json] import std/uri import browsers import types, logging, renders @@ -9,16 +9,16 @@ proc relToRoot*(doc: NbDoc, url: string): string = if isAbsolute(url) or isAbsolute(parseUri(url)): url else: - doc.context["path_to_root"].vString / url + doc.context{"path_to_root"}.getStr / url -proc write*(doc: var NbDoc) = +proc write*(nb: Nb) = log "current directory: " & getCurrentDir() - let dir = doc.filename.splitFile().dir + let dir = nb.doc.filename.splitFile().dir if not dir.dirExists: log "creating directory: " & dir createDir(dir) - log "saving file: " & doc.filename - writeFile(doc.filename, render(doc)) + log "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 00000000..3ad5e911 --- /dev/null +++ b/src/nimib/globals.nim @@ -0,0 +1,5 @@ +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 +var nbToMd* = NbRender() \ No newline at end of file diff --git a/src/nimib/jsons.nim b/src/nimib/jsons.nim new file mode 100644 index 00000000..5afcfe00 --- /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 5e7d0321..9cd5ec58 100644 --- a/src/nimib/jsutils.nim +++ b/src/nimib/jsutils.nim @@ -1,5 +1,21 @@ import std / [macros, macrocache, tables, strutils, strformat, sequtils, sugar, os, hashes] -import ./types +import ./types, ./nimibSugars, ./globals, ./jsons, ./blocks, ./renders +from std/jsonutils import nil + + +newNbBlock(NbJsFromCode): # should inherit from nbCode, but it is defined in nimib.nim... + code: string + transformedCode: string + putAtTop: bool + toHtml: + "" + +newNbBlock(NbJsFromCodeOwnFile): + code: string + transformedCode: string + jsCode: string + toHtml: + &"" proc contains(tab: CacheTable, keyToCheck: string): bool = for key, val in tab: @@ -179,7 +195,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 +242,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) + 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 00000000..c8ae49d9 --- /dev/null +++ b/src/nimib/nimibSugars.nim @@ -0,0 +1,216 @@ +import std / [macros, strutils, sequtils, genasts] + +proc parseCallStmt(n: NimNode): tuple[lhs: string, rhs: NimNode] = + n.expectKind nnkCall + if n[1].len > 1: # toHtml case + (n[0].strVal, n[1]) + else: # field case + (n[0].strVal, n[1][0]) + +#[ this is what fields with default values look like +Call + Ident "a" + StmtList + Asgn + Ident "string" + StrLit "" +]# + +func getTypeFields*(typeSym: NimNode): seq[NimNode] = + ## return seq[nnkIdentDefs] + typeSym.expectKind nnkSym + # add all own fields + # look through typeSym.getImpl and look for OfInherit and recurse! + let typeDef = typeSym.getImpl + #debugecho "typeDef of ", typeSym.repr, ":", typedef.repr + let ofInherit = typeDef[2][0][1] + if ofInherit.kind == nnkOfInherit: + let parentTypeSym = ofInherit[0] + if parentTypeSym.strVal != "RootObj": + result.add parentTypeSym.getTypeFields + let recList = typeDef[2][0][2] + result.add recList.children.toSeq + +func removePostfix*(identDef: NimNode): NimNode = + result = identDef.copyNimTree() + if result[0].kind == nnkPostfix: + result[0] = result[0][1] + +#[ var procParams = @[capitalizedTypeName.ident] & fieldsList +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 +]# +macro generateBlockInitializer*(typeName: typed): untyped = + var fieldsList = typeName.getTypeFields().mapIt(it.removePostfix).filterIt(it[0].strVal != "kind") + for identDef in fieldsList: + identDef[2] = genAst(fieldType = identDef[1]): + default(typedesc[fieldType]) + #echo fieldsList.mapIt(it.repr) + + let procParams = @[typeName] & fieldsList + var objectConstructor = nnkObjConstr.newTree( + typeName + ) + objectConstructor.add newColonExpr(ident"kind", typeName.strVal.newLit) + for identDef in fieldsList: + let fieldName = identDef[0] + objectConstructor.add newColonExpr(fieldName, fieldName) + let procBody = newStmtList(objectConstructor) + let procName = postfix(ident("new" & typeName.strVal), "*") + let initializer = newProc(procName, procParams, procBody) + #echo "init:\n", initializer.repr + return initializer + +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: seq[NimNode] + var exportedFieldsList = nnkRecList.newTree() + for (fName, fType) in fields: + fieldsList.add newIdentDefs(fName.ident, fType) + exportedFieldsList.add newIdentDefs(postfix(fName.ident, "*"), fType) + + # TODO: add parent list of fields to fieldsList as well! + # This probaly has to be done in a typed macro? + # In that case, let the typed macro construct the initializer! + + let typeDefinition = nnkTypeSection.newTree(nnkTypeDef.newTree( + postfix(capitalizedTypeName.ident, "*"), + newEmptyNode(), # generic + nnkRefTy.newTree( + nnkObjectTy.newTree( + newEmptyNode(), # pragma + nnkOfInherit.newTree( + parentType + ), + exportedFieldsList + ) + ) + )) + + #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... + + let initializer = genAst(typeName = capitalizedTypeName.ident): + generateBlockInitializer(typeName) + + #[ var procParams = @[capitalizedTypeName.ident] & fieldsList + 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 = "".newLit + if body.len > 0: + for i, line in body: + # 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) + + # Only add a newline if the line contains anything and isn't the last line + # Also if it already ends with a newline we don't have to add one + result = genAst(result=result, line=line, isLast=newLit(i==body.len-1)): + let lineVal = line + if not isLast and lineVal.len > 0 and lineVal[^1] != '\n': + result & lineVal & "\n" + else: + result & lineVal + + + + diff --git a/src/nimib/renders.nim b/src/nimib/renders.nim index a2141433..0a323685 100644 --- a/src/nimib/renders.nim +++ b/src/nimib/renders.nim @@ -1,18 +1,68 @@ -import std / [strutils, tables, sugar, os, strformat, sequtils] -import ./types, ./jsutils, ./logging, markdown, mustache +import std / [strutils, tables, sugar, os, strformat, sequtils, json] +import ./types, markdown, mustache, ./jsutils, ./nimibSugars, ./themes, ./globals, ./jsons, ./logging import highlight import mustachepkg/values +from std/jsonutils import nil -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 nbDocToHtml*(blk: NbBlock, nb: Nb): string = + let doc = blk.NbDoc + let docJson = %[] # it's unused + result = withNewlines: + "" + """""" + nb.renderPartial("head", docJson) + "" + nb.renderPartial("header", docJson) + nb.renderPartial("left", docJson) + mainToHtml(doc, nb) + nb.renderPartial("right", docJson) + nb.renderPartial("footer", docJson) + "" + +addNbBlockToJson(NbDoc) # should this be here? +nbToHtml.funcs["NbDoc"] = nbDocToHtml + +func nbCodeSourcePartial*(blk: JsonNode, nb: Nb): string = + let code = blk{"code"}.getStr + if code.len > 0: + &"
{code.highlightNim}
" + else: + "" + +nbToHtml.partials["nbCodeSource"] = nbCodeSourcePartial + +func nbCodeOutputPartial*(blk: JsonNode, nb: Nb): string = + let output = blk{"output"}.getStr + if output.len > 0: + &"
{output}
" + else: + "" + +nbToHtml.partials["nbCodeOutput"] = nbCodeOutputPartial + +func markdownToHtml*(markdownText: string): string = + {.cast(noSideEffect).}: # not sure why markdown is marked with side effects + markdown(markdownText, config=initGfmConfig()) + +func nbTextPartial*(blk: JsonNode, nb: Nb): string = + let text = blk{"text"}.getStr + markdownToHtml(text) + +nbToHtml.partials["nbText"] = nbTextPartial + +proc useHtmlBackend*(nb: var Nb) = + nb.backend = nbToHtml -proc useHtmlBackend*(doc: var NbDoc) = - doc.partials["nbText"] = "{{&outputToHtml}}" +#[ doc.partials["nbText"] = "{{&outputToHtml}}" doc.partials["nbCode"] = """ {{>nbCodeSource}} {{>nbCodeOutput}}""" @@ -62,9 +112,36 @@ Your browser does not support the audio element. doc.renderProcs = initTable[string, NbRenderProc]() doc.renderProcs["mdOutputToHtml"] = mdOutputToHtml doc.renderProcs["highlightCode"] = highlightCode - doc.renderProcs["compileNimToJs"] = compileNimToJs + doc.renderProcs["compileNimToJs"] = compileNimToJs ]# + +#[ func nbDocToMd*(blk: NbBlock, nb: Nb): string = + let doc = blk.NbDoc + result = nbContainerToHtml(doc, nb) + +nbToMd.funcs["NbDoc"] = nbDocToMd + +proc nbTextToMd*(blk: NbBlock, nb: Nb): string = + let blk = blk.NbText + +nbToMd.funcs["NbText"] = nbTextToMd + +proc nbCodeToMd*(blk: NbBlock, nb: Nb): string = + discard + +nbToMd.funcs["NbCode"] = nbCodeToMd ]# + +func nbDocToMd*(blk: NbBlock, nb: Nb): string = + let doc = blk.NbDoc + let docJson = %[] # it's unused + result = withNewlines: + nbContainerToMd(doc, nb) + +nbToMd.funcs["NbDoc"] = nbDocToMd + +proc useMdBackend*(nb: var Nb) = + nb.backend = nbToMd -proc useMdBackend*(doc: var NbDoc) = +#[ doc.partials["document"] = """ {{#blocks}} @@ -140,3 +217,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/sources.nim b/src/nimib/sources.nim index 55ca7d43..e3742b40 100644 --- a/src/nimib/sources.nim +++ b/src/nimib/sources.nim @@ -33,6 +33,7 @@ proc startPos(node: NimNode): Pos = else: result = toPos(node.lineInfoObj()) for child in node.children: + if child.kind == nnkEmpty: continue let childPos = child.startPos() # If we can't get the line info for some reason, skip it! if childPos.line == 0: continue @@ -65,7 +66,9 @@ proc finishPos*(node: NimNode): Pos = result = toPos(node.lineInfoObj()) proc isCommandLine*(s: string, command: string): bool = - nimIdentNormalize(s.strip()).startsWith(nimIdentNormalize(command)) + # how do we handle nb.code as well as nbCode? + # check for contains instead? + nimIdentNormalize(s.strip()).startsWith(nimIdentNormalize(command)) or nimIdentNormalize(command) in nimIdentNormalize(s.strip()) proc isCommentLine*(s: string): bool = s.strip.startsWith('#') @@ -86,7 +89,7 @@ proc findStartLine*(source: seq[string], startPos: Pos): int = return startPos.line - 1 -proc findEndLine*(source: seq[string], command: string, startLine, endPos: int): int = +proc findEndLine*(source: seq[string], startsOnCommandLine: bool, startLine, endPos: int): int = result = endPos # Handle if line is an unclosed triple-quote string if source[endPos].count("\"\"\"") mod 2 == 1: @@ -94,10 +97,10 @@ proc findEndLine*(source: seq[string], command: string, startLine, endPos: int): while result < source.high and source[result].count("\"\"\"") mod 2 == 0: inc result # Handle if there are ending comments - let startsOnCommandLine = source[startLine].isCommandLine(command) + #let startsOnCommandLine = source[startLine].isCommandLine(command) if result > startLine or not startsOnCommandLine: let baseIndent = - if source[startLine].isCommandLine(command): + if startsOnCommandLine: skipWhile(source[startLine], {' '}) + 1 # we want to add indent here. # this is problematic because we don't know the indentation of the code block because # we don't know the indentation size used. So we just add 1 and check that it is larger or equal. @@ -107,19 +110,18 @@ proc findEndLine*(source: seq[string], command: string, startLine, endPos: int): while result < source.high and (source[result+1].startsWith(baseIndentStr) or source[result+1].isEmptyOrWhitespace): inc result -proc getCodeBlock*(source, command: string, startPos, endPos: Pos): string = +proc getCodeBlock*(source: string, startPos, endPos: Pos): string = ## Extracts the code in source from startPos to endPos with additional processing to get the entire code block. let rawLines = source.splitLines() let rawStartLine = startPos.line - 1 let rawStartCol = startPos.column - 1 var startLine = findStartLine(rawLines, startPos) - var endLine = findEndLine(rawLines, command, startLine, endPos.line - 1) - - var lines = rawLines[startLine .. endLine] - let startsOnCommandLine = block: - let preline = lines[0][0 ..< rawStartCol] + let preline = rawLines[startLine][0 ..< rawStartCol] startLine == rawStartLine and (not preline.isEmptyOrWhitespace) and (not (preline.nimIdentNormalize.strip() in ["for", "type"])) + var endLine = findEndLine(rawLines, startsOnCommandLine, startLine, endPos.line - 1) + + var lines = rawLines[startLine .. endLine] if startsOnCommandLine: lines[0] = lines[0][rawStartCol .. ^1].strip() @@ -143,7 +145,7 @@ proc getCodeBlock*(source, command: string, startPos, endPos: Pos): string = preserveIndent = not preserveIndent result = lines.join("\n") -macro getCodeAsInSource*(source: string, command: static string, body: untyped): string = +macro getCodeAsInSource*(source: string, body: untyped): string = ## Returns string for the code in body from source. # substitute for `toStr` in blocks.nim let startPos = startPos(body) @@ -155,12 +157,15 @@ macro getCodeAsInSource*(source: string, command: static string, body: untyped): let startPosLit = startPos.newLit result = quote do: - if `filename` notin nb.sourceFiles: - nb.sourceFiles[`filename`] = readFile(`filename`) + if `filename` notin nb.doc.sourceFiles: + nb.doc.sourceFiles[`filename`] = readFile(`filename`) doAssert `endFilename` == `filename`, """ Code from two different files were found in the same nbCode! If you want to mix code from different files in nbCode, use -d:nimibCodeFromAst instead. If you are not mixing code from different files, please open an issue on nimib's Github with a minimal reproducible example.""" - getCodeBlock(nb.sourceFiles[`filename`], `command`, `startPosLit`, `endPosLit`) \ No newline at end of file + getCodeBlock(nb.doc.sourceFiles[`filename`], `startPosLit`, `endPosLit`) + +template getCode*(body: untyped): string = + getCodeAsInSource(nb.doc.source, body).strip \ No newline at end of file diff --git a/src/nimib/themes.nim b/src/nimib/themes.nim index 94db5670..43149128 100644 --- a/src/nimib/themes.nim +++ b/src/nimib/themes.nim @@ -1,6 +1,127 @@ -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 +# 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* = """""" + +func getTitle*(doc: NbDoc): string = + doc.context{"title"}.getStr("nimib document") + + +# All of this should be moved to renders.nim? +func headToHtml*(blk: JsonNode, nb: Nb): string = + result = withNewlines: + fmt""" + + {nb.doc.getTitle} + + + + {nb.doc.context.getOrDefault("stylesheet").getStr} + {nb.doc.context.getOrDefault("highlight").getStr} + {nb.doc.context.getOrDefault("nb_style").getStr} + {nb.doc.context.getOrDefault("latex").getStr} + """ + if not nb.doc.context{"disableHighlightJs"}.getBool(false): + nb.doc.context{"highlightJs"}.getStr + "" + + +func madeWithNimibToHtml*(): string = + """made with nimib 🐳""" + +func homeLinkToHtml*(homePath: string): string = + fmt"""🏡""" + +func headerLeftToHtml*(blk: JsonNode, nb: Nb): string = + homeLinkToHtml(nb.doc.context{"path_to_root"}.getStr("/")) + +func headerCenterToHtml*(blk: JsonNode, nb: Nb): string = + let title = nb.doc.getTitle() + fmt"{title}" + +func headerRightToHtml*(blk: JsonNode, nb: Nb): string = + if not isNil(nb.doc.context{"github_remote_url"}): + let githubRemoteUrl = nb.doc.context{"github_remote_url"}.getStr + let githubLogo = nb.doc.context{"github_logo"}.getStr(githubLogoLight) + result = fmt"""{github_logo}""" + +func headerToHtml*(blk: JsonNode, nb: Nb): string = + result = withNewlines: + "
" + """
""" + " " & nb.renderPartial("header_left", blk) & "" + " " & nb.renderPartial("header_center", blk) & "" + " " & nb.renderPartial("header_right", blk) & "" + "
" + "
" + "
" + +func leftToHtml*(blk: JsonNode, nb: Nb): string = + "" + +func mainToHtml*(blk: NbBlock, nb: Nb): string = + result = withNewlines: + "
" + nbContainerToHtml(blk, nb) + "
" + +func rightToHtml*(blk: JsonNode, nb: Nb): string = + "" + +func footerLeftToHtml*(blk: JsonNode, nb: Nb): string = + madeWithNimibToHtml() + +func footerCenterToHtml*(blk: JsonNode, nb: Nb): string = + "" + +func showSourceButtonToHtml*(blk: JsonNode, nb: Nb): string = + """""" + +func footerRightToHtml*(blk: JsonNode, nb: Nb): string = + showSourceButtonToHtml(blk, nb) + +func sourceSectionToHtml*(blk: JsonNode, nb: Nb): string = + fmt""" +
+
{nb.doc.context.getOrDefault("source_highlighted").getStr}
+
+ """ + +func showSourceScriptToHtml*(blk: JsonNode, nb: Nb): string = + """ + + """ + +func footerToHtml*(blk: JsonNode, nb: Nb): string = + result = withNewlines: + "" + nb.renderPartial("source_section", blk) + nb.renderPartial("show_source_script", blk) + + +#[ # TODO: Make these old partials into procs with inputs so they can be reused! const document* = """ @@ -38,7 +159,7 @@ const main* = """ {{&.}} {{/blocks}} -""" +""" ]# # https://css-tricks.com/emojis-as-favicons/ changed font-size to 80 to fit whale const faviconWhale* = """""" @@ -86,7 +207,7 @@ figcaption { """ -const header* = """ +#[ const header* = """
{{> header_left }} @@ -97,10 +218,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* = """