{"id":20530286,"url":"https://github.com/zoomten/nailit","last_synced_at":"2025-10-18T02:43:46.892Z","repository":{"id":218364638,"uuid":"729160045","full_name":"ZoomTen/nailit","owner":"ZoomTen","description":"Simple (but limited) literate programming tool written in Nim","archived":false,"fork":false,"pushed_at":"2024-03-31T08:16:54.000Z","size":103,"stargazers_count":12,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-16T11:52:36.529Z","etag":null,"topics":["literate-programming","nim-lang"],"latest_commit_sha":null,"homepage":"https://zoomten.github.io/nailit/","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZoomTen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-08T15:00:26.000Z","updated_at":"2024-07-16T19:20:06.000Z","dependencies_parsed_at":"2024-11-15T23:36:21.741Z","dependency_job_id":"0831976e-2baa-48bb-923a-0696b178d234","html_url":"https://github.com/ZoomTen/nailit","commit_stats":null,"previous_names":["zoomten/nailit"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZoomTen%2Fnailit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZoomTen%2Fnailit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZoomTen%2Fnailit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZoomTen%2Fnailit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZoomTen","download_url":"https://codeload.github.com/ZoomTen/nailit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242133968,"owners_count":20077171,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["literate-programming","nim-lang"],"created_at":"2024-11-15T23:36:11.925Z","updated_at":"2025-10-18T02:43:41.855Z","avatar_url":"https://github.com/ZoomTen.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NailIt\n\nA quite minimal [literate programming](http://www.literateprogramming.com/) tool, capable of formatting code with explanation, linking to sections of code and backreferencing other sections of code.\n\nThe tool converts from a Markdown file into HTML, fit for reading online or printing, if you want.\n\nThe tool supports converting only one Markdown file at the moment, if you want to use multiple files you'll have to combine them somehow… `cat *.md \u003e onesource.md`?\n\nAlso, the tool does not at the moment support appending code blocks \"dynamically\", so you have to uh… *nail it* I guess. It may be better that way, anyway—at least, in a \"documentation\" instead of \"tutorial\" setting.\n\n## Building\n\nThe repository is located at https://github.com/ZoomTen/nailit.\n\nRequires [Nim](https://nim-lang.org/) ≥1.6.x. The standard distribution should include the `nimble` tool, use `nimble build` to make a binary called `nailit`.\n\n## Usage\n\n``` command line arguments\nNailIt - a simple literate programming tool.\n\nUsage:\n  nailit weave [--template=\u003ctemplate.html\u003e] \u003csource.md\u003e [\u003cout.html\u003e]\n  nailit tangle \u003csource.md\u003e \u003cdestdir/\u003e\n  nailit blocks \u003csource.md\u003e\n  nailit (-h | --help)\n  nailit --version\n\nweave = generate a human-readable HTML document\n        from literate programs.\n\ntangle = generate compileable source code from\n          literate programs.\n\nblocks = see what blocks NailIt sees.\n```\n\n## Literate program structure\n\nLiterate programs consist of **code blocks** and **prose blocks**. This tool accepts literate programs in the form of Markdown-formatted documents.\n\n**Code blocks** are, well, the actual program source code. To make a code block, surround code with a single line of `\\`\\`\\`` before and after the code portion. The `\\`\\`\\`` line *before* the code can take one of four forms:\n\n1. `\\`\\`\\``: starts an unnamed block interpreted as plain text;\n2. `\\`\\`\\`lang`: starts an unnamed block interpreted as code in `lang`;\n3. `\\`\\`\\`lang name of block`: starts a block named `name of block` interpreted as code in `lang`;\n4. `\\`\\`\\` name of block`: starts a block named `name of block` interpreted as plain text. **In this form, at least one space is needed before the block's name!**\n\nCode block names that start with a `/` will be interpreted as file output relative to the `destdir` specified when invoking `nailit tangle`.\n\nInside a code block, you can refer to other code blocks like so: `@{Name of other code block}`. They **must** live in its own line, with optional indentation. Indenting these references will add indentation to the inserted code block when tangling it, so you must keep that in mind when using whitespace-sensitive languages.\n\n**Prose blocks** are paragraphs and other stuff *around* the code blocks that explain what the code does and why it does. They are formatted as as Markdown… [Nim-flavored Markdown](https://nim-lang.org/docs/markdown_rst.html), at least. Because it uses Nim's own Markdown compiler, NailIt is relatively self-contained—you don't need an external tool for helping with the Markdown-to-HTML conversion, which is either a feature or a limitation depending on your point of view.\n\n## Limitations\n\n(or, what similar programs might do that NailIt doesn't do)\n\n* Code block references must live in its own line.\n* No support for multiple source files.\n* No support for creating multi-page output. Though, the single huge page *could* be made into one… if you print it to PDF through your browser :p\n* No support for appending to code blocks, only replacing them (will output a warning).\n* No support for syntax highlighting natively, although you may use a JavaScript-based solution like [Prism.js](https://prismjs.com/).\n* Does not automatically insert prose blocks inside of tangled code blocks as comments.\n* Weaving and tangling must be done separately, e.g. `nailit weave source.md source.html \u0026\u0026 nailit tangle source.md src/`. There's not really a technical reason for this, I just want to know what I'm doing.\n* No support for line numbers in the weaved output yet.\n\n## Design Considerations\n\n* Allow code blocks to be written anywhere in the literate program and let NailIt compile them into a program that makes sense, per the Knuthian definition of literate programming.\n* Allow flexibility in laying out a literate program.\n* The files it reads should remain compatible with existing Markdown engines, like GitHub's renderer.\n* Keep it simple and probably naive.\n* Reference code block names directly in the output, rather than using section or index numbers, which should help readability by making it obvious what a code block is used in.\n\n## Styling Weaved Output\n\nThe body of the weaved output consists of HTML prose (not wrapped in anything… yet?) and code blocks, formatted like this:\n\n```html\n\u003cdiv id=\"codeblocktitle\" class=\"code-block\"\u003e\n  \u003cheader class=\"block-title\"\u003e\n    \u003ca href=\"#codeblocktitle\"\u003eCode block title\u003c/a\u003e\n  \u003c/header\u003e\n  \u003cpre\u003e\n    \u003ccode class=\"cb-content\"\u003e\n      Here's a bit of code...\n    \u003c/code\u003e\n    \u003ccode class=\"cb-reference\"\u003e\n      \u003ca href=\"#someothercode\"\u003e@ {Some other code}\u003c/a\u003e\n    \u003c/code\u003e\n    \u003ccode class=\"cb-content\"\u003e\n      Here's another bit of code...\n    \u003c/code\u003e\n  \u003c/pre\u003e\n  \u003cfooter class=\"used-by\"\u003e\n    Used by\n    \u003ca href=\"#yetanotherpieceofcode\"\u003eYet another piece of code\u003c/a\u003e,\n    \u003ca href=\"#andanother\"\u003eand another\u003c/a\u003e\n  \u003c/footer\u003e\n\u003c/div\u003e\n```\n\nThe containing `\u003cdiv\u003e` will have `language-*` classes if a language is specified in the corresponding code block in the literate program. Additionally, the \"used by\" footer will not be present if a code block stands alone, not referenced by any other code block. And the header would disappear when using anonymous code blocks.\n\n(yeah, I need to escape the @{code referencing} stuff… :\\\\)\n\n## Source Code\n\nThis README contains NailIt's entire source code! However for convenience and bootstrapping, this repo also provides the sources generated off this README. It also serves as a practical explanation on what literate programs NailIt can process.\n\nTo make the compileable source code from this README, do:\n\n```sh\nnimble run -- tangle README.md .\n```\n\nTo generate a literate program as HTML from this README, do:\n\n```sh\nnimble run -- weave README.md index.html\n```\n\n(The `nimble run --` command is used here to make it more straight-forward, but you can instead build and just use `./nailit` directly)\n\n### Entry point\n\nThe entry point to the program is about what you'd expect: Parse command line arguments, do stuff accordingly. The really nice [docopt](https://github.com/docopt/docopt.nim) library is used to transform the command line help string into actual arguments the program can parse. The commands, at least, stay in-sync *and* self-documenting.\n\n```nim main program\nlet args = \"\"\"\n@{command line arguments}\n\"\"\".docopt(\n  version = \"NailIt 0.2.0\"\n  )\n\nlet blocks =\n  open($args[\"\u003csource.md\u003e\"]).getBlocks()\n\nif args[\"weave\"].to_bool():\n  @{call weave command}\n\nif args[\"tangle\"].to_bool():\n  @{call tangle command}\n\nif args[\"blocks\"].to_bool():\n  @{call blocks command}\n```\n\n### Blocks\n\nBlocks are just text with attributes that make it either \"part of the explanation\" or \"part of the code\". Prose blocks are straight-forward, containing only content. Code blocks however, have additional metadata.\n\n```nim block type definition\ntype\n  BlockType = enum\n    Prose\n    Code\n\n  Block = object\n    content: string\n    case kind: BlockType\n    of Code:\n      name: string\n      language: string\n    else:\n      discard\n```\n\n#### Parsing blocks from the document\n\nBasically, parsing is done on a line-by-line basis. This function takes in a file input and spits out the list of blocks resulting from that file.\n\n```nim get blocks from source function\nproc getBlocks(f: File): seq[Block] =\n  @{helper function to add a block}\n\n  var\n    totalBlocks: seq[Block] = @[]\n    isCodeBlock = false\n\n  var\n    contentBuffer = \"\"\n    nextNameBuffer = \"\"\n    nextLangBuffer = \"\"\n\n  for line in lines(f):\n    @{parse a line and make new blocks}\n\n  return totalBlocks\n```\n\nWhile parsing, the program looks for these specific patterns:\n\n* `codeBlockPtn` scans for the start and end of code block *definitions*, in the 4 forms described earlier in this document.\n* `codeBlockRefPtn` scans for code block *references*, like `@{named block}`\n* `codeBlockRefSpacesPtn` is like `codeBlockRefPtn`, except it grabs whatever leading spaces are in it as well.\n\n```nim regex patterns\nconst\n  codeBlockPtn = re2\"^```$|^```(\\w+)$|^```(\\w+)\\s+(.+)$|^```\\s+(.+)$\"\n  codeBlockRefPtn = re2\"(@\\{(.+)\\})\"\n  codeBlockRefSpacesPtn = re2\"(?m)^(\\s*?)@\\{(.+?)\\}\"\n```\n\nThe two types of blocks in the markdown document live separately and cannot be nested, i.e. no code blocks in prose blocks and vice versa, no code blocks within code blocks, etc. On every line, when one of the code block patterns are found, a switch that asks \"is the current block a code block?\", is toggled.\n\n```nim parse a line and make new blocks\nif (var m: RegexMatch2; line.match(codeBlockPtn, m)):\n  totalBlocks.addBlock(\n    (if isCodeBlock: Code else: Prose),\n    contentBuffer,\n    nextNameBuffer,\n    nextLangBuffer\n  )\n  # TODO: BUG a blank line in place of this line makes the\n  # below line have incorrect indentation\n  @{set the name for the next block conditionally}\n  @{set the language for the next block conditionally}\n  contentBuffer = \"\"\n  isCodeBlock = not isCodeBlock\nelse:\n  contentBuffer \u0026= line \u0026 \"\\n\"\n```\n\nThe nature of this loop means that if a code block begins the document, it will come after an empty prose block. Not that it matters, anyway. Since the code block to be added is not actually inserted until it hits an ending `\\`\\`\\``, setting metadata for that code block is deferred.\n\nThe [regex library](https://github.com/nitely/nim-regex) I'm using expresses empty matches by having its begin index greater than the end index, but I wanna be lazy, so here's a helper function.\n\n```nim function to determine if a regex match is empty\nproc isEmptyMatch(s: Slice[int]): bool {.inline.} =\n  return (s.a \u003e s.b)\n```\n\nGroups 2 (inside a named code block with language) and 3 (inside a named plain text code block) contain the name of the new block, so I'll check for both.\n\n```nim set the name for the next block conditionally\nnextNameBuffer = (\n  if not (m.group(2).isEmptyMatch()): line[m.group(2)].strip()\n  elif not (m.group(3).isEmptyMatch()): line[m.group(3)].strip()\n  else: \"\"\n)\n```\n\nAs are the language identifier in groups 0 (inside an anonymous code block) and 1 (inside a named code block). Note here that group 0 really means the first group, and not \"the entire match\" as Python would have it.\n\n```nim set the language for the next block conditionally\nnextLangBuffer = (\n  if not (m.group(0).isEmptyMatch()): line[m.group(0)]\n  elif not (m.group(1).isEmptyMatch()): line[m.group(1)]\n  else: \"\"\n)\n```\n\nThis helper function exists to handle things like spaces before and after the content, as well as potentially other issues should they come in the future.\n\n```nim helper function to add a block\nproc addBlock(\n    blocks: var seq[Block],\n    parseAs: BlockType,\n    contentBuf: string,\n    nameBuf: string = \"\",\n    langBuf: string = \"\"\n): void =\n  case parseAs\n  of Prose:\n    blocks.add Block(\n      kind: Prose,\n      content: contentBuf\n    )\n  of Code:\n    blocks.add Block(\n      kind: Code,\n      name: nameBuf,\n      content: (\n        @{trim spaces on either end of the content}\n      ),\n      language: langBuf\n    )\n```\n\n#### Cleaning up block content\n\nHere's where I trim the spaces. The final line is what will ultimately be the value for `content`. I do like how Nim lets me do this kinda thing.\n\n```nim trim spaces on either end of the content\nvar contentStripped = contentBuf\n\nif contentStripped.len == 1:\n  contentStripped = \"\"\nelse:\n  if contentStripped[0] == '\\n':\n    contentStripped = contentStripped[1 ..^ 1]\n  if contentStripped[^1] == '\\n':\n    contentStripped = contentStripped[0 ..^ 2]\n\ncontentStripped\n```\n\n### Weave\n\nThe `weave` command compiles an HTML page from a literate program.\n\nFirst, the blocks are transformed into an HTML string of the entire contents using the [weave](#weavefunction) function. Then, it is inserted into an HTML template using [intoHtmlTemplate](#insertweavedintohtmltemplate). This template is set via the option `--template`—although optional, as the command has a \"default\" template that it uses. If an output file (2nd argument) is not provided, the output will simply be in `stdout`.\n\n```nim call weave command\nlet weaved = blocks.weave().intoHtmlTemplate(\n  inputTemplate = (\n    if args[\"--template\"].kind == vkNone:\n      \"\"\n    else:\n      open($args[\"--template\"]).readAll()\n  ),\n  title = $args[\"\u003csource.md\u003e\"], # TODO\n)\n\nif args[\"\u003cout.html\u003e\"].kind == vkNone:\n  echo weaved\nelse:\n  open($args[\"\u003cout.html\u003e\"], fmWrite).write(weaved)\nquit(0)\n```\n\n#### The weave function\n\nHere's the function that turns the list of blocks processed earlier into an HTML string.\n\n```nim weave function\nproc weave(blocks: seq[Block]): string =\n  var reflist: Table[string, CountTable[string]]\n  var generatedHtml = \"\"\n\n  @{initialize code block references list}\n  @{count code block references}\n  @{helper function to transform names to links}\n\n  # turn each block to stuff\n  for txblock in blocks:\n    case txblock.kind\n    of Code:\n      @{convert a code block into html}\n    of Prose:\n      @{convert a prose block into html}\n  return generatedHtml\n```\n\n#### Counting code block references\n\nA code block is usually referenced by other code blocks, so for every named code block I need to track how many times they're referenced or invoked in other code blocks. Just in case I need to show it.\n\n```nim initialize code block references list\nfor txblock in blocks:\n  case txblock.kind\n  of Code:\n    if not reflist.hasKey txblock.name:\n      reflist[txblock.name] = initCountTable[string](0)\n  of Prose:\n    discard\n```\n\nFor each block I then add 1 to the reference count of each other code block referenced within this code block. Here I can also do some checking, warning you that you might have referenced a block that doesn't even exist at all.\n\n```nim count code block references\nfor txblock in blocks:\n  case txblock.kind\n  of Code:\n    for m in txblock.content.findAll(codeBlockRefPtn):\n      let keyName = txblock.content[m.captures[1]]\n\n      # skip empty names\n      if keyName.len \u003c 1: continue\n\n      if reflist.hasKey keyName:\n        reflist[keyName].inc txblock.name\n      else:\n        stderr.writeLine \"WARNING: key \" \u0026 keyName \u0026 \" not found!\"\n  of Prose:\n    discard\n```\n\n#### Generating the prose block HTML\n\nConverting prose blocks to HTML is trivial: just use the `rstToHtml` function on the entire input and append it to the HTML. Although there is a bit of a quirk when the contents are not preceded with a blank line: the first paragraph will be text whereas the others would be surrounded in `\u003cp\u003e`. This can add pain to layout and styling, and so I've put a `.. raw:: html` hack to force the first paragraph to be surrounded in `\u003cp\u003e`.\n\n```nim convert a prose block into html\nlet toParaHack = \".. raw:: html\\n\\n\" \u0026 txblock.content\ngeneratedHtml \u0026=\n  toParaHack.rstToHtml(\n    {\n      roSupportMarkdown, roPreferMarkdown, roSandboxDisabled,\n      roSupportRawDirective,\n    },\n    modeStyleInsensitive.newStringTable(),\n  )\n```\n\n#### Generating the code block HTML\n\nOn the other hand, converting code blocks aren't so trivial. At minimum the code block needs to have escapes in order for them not to be interpreted as HTML code when I don't want it, which can lead to incorrect code displays. Then there's also the extra metadata that needs to be laid out so as to easily identify and navigate between them.\n\n```nim convert a code block into html\nlet escapedCode =\n  @{make the code block html-friendly}\n\nlet normName = txblock.name.normalize()\n\n# start writing converted code block\ngeneratedHtml \u0026= (\n  @{code block html start}\n)\n\n# if the block is used somewhere else, say so\nif txblock.name.len \u003e 0 and reflist[txblock.name].len \u003e 0:\n  @{generate backlinks list for html code block}\n\n# end write block\ngeneratedHtml \u0026= (\n  @{code block html end}\n)\n```\n\nFirst I escape the common HTML characters, and then turn all code block references into links.\n\n```nim make the code block html-friendly\ntxblock.content\n  .replace(\"\u0026\", \"\u0026amp;\")\n  .replace(\"\u003c\", \"\u0026lt;\")\n  .replace(\"\u003e\", \"\u0026gt;\")\n  .replace(codeBlockRefPtn, nameAsLink)\n```\n\nThe link-replacement is done by this helper function:\n\n```nim helper function to transform names to links\nproc nameAsLink(m: RegexMatch2, s: string): string =\n  return\n    \"\u003c/code\u003e\u003ccode class=\\\"cb-reference\\\"\u003e\u003ca href=\\\"#\" \u0026\n      s[m.group(1)].normalize() \u0026\n    \"\\\"\u003e\" \u0026\n      s[m.group(0)] \u0026\n    \"\u003c/a\u003e\u003c/code\u003e\u003ccode class=\\\"cb-content\\\"\u003e\"\n```\n\nEvery one of these links needs to refer to valid HTML identifiers, which, to make it consistent, I'll have to make a helper function to convert from the code block's name to a weird HTML identifier.\n\n```nim function to normalize labels\nproc normalize(s: string): string =\n  return s\n    .replace(\"_\",\"\")\n    .replace(\" \",\"\")\n    .tolowerascii()\n```\n\nAfter I've converted the main content into something presentable, I can wrap it in an HTML container, having a title tab if the code block has a name, but a plain pre otherwise. In them I also add language information via a class, so that external tools or JavaScript would know what to do with them.\n\n```nim code block html start\nif txblock.name.len \u003e 0:\n  @{starting html for named code block}\nelse:\n  @{starting html for anonymous code block}\n```\n\n```nim starting html for named code block\n\"\u003cdiv class=\\\"code-block\" \u0026 (\n    if txblock.language.strip() == \"\": \"\"\n    else: \" language-\" \u0026 txblock.language\n  ) \u0026 \"\\\" id=\\\"\" \u0026 normName \u0026 \"\\\"\u003e\" \u0026\n  \"\u003cheader class=\\\"block-title\\\"\u003e\" \u0026\n    \"\u003ca href=\\\"#\" \u0026 normName \u0026 \"\\\"\u003e\" \u0026 txblock.name \u0026 \"\u003c/a\u003e\" \u0026\n  \"\u003c/header\u003e\" \u0026\n  \"\u003cpre\u003e\u003ccode class=\\\"cb-content\\\"\u003e\" \u0026\n    escapedCode \u0026\n  \"\u003c/code\u003e\u003c/pre\u003e\"\n```\n\n```nim starting html for anonymous code block\n\"\u003cdiv class=\\\"code-block\" \u0026 (\n  if txblock.language.strip() == \"\": \"\"\n  else: \" language-\" \u0026 txblock.language\n) \u0026 \"\\\"\u003e\" \u0026\n  \"\u003cpre\u003e\u003ccode class=\\\"cb-content\\\"\u003e\" \u0026\n    escapedCode \u0026\n  \"\u003c/code\u003e\u003c/pre\u003e\"\n```\n\n```nim code block html end\n\"\u003c/div\u003e\"\n```\n\nThe backlinks list take advantage of the whole block reference-counting thing from earlier. It can help navigate back and forth between sections of code, answering the question of \"Hmm, where is *this* used?\"\n\n```nim generate backlinks list for html code block\ngeneratedHtml \u0026= \"\u003cfooter class=\\\"used-by\\\"\u003eUsed by \"\n\nfor i in reflist[txblock.name].keys:\n  let normI = i.normalize()\n  generatedHtml \u0026=\n    \"\u003ca href=\\\"#\" \u0026 normI \u0026 \"\\\"\u003e\" \u0026 i \u0026\n    # \" \u0026times; \" \u0026 $(reflist[txblock.name][i]) \u0026\n    \"\u003c/a\u003e \"\ngeneratedHtml \u0026= \"\u003c/footer\u003e\"\n```\n\n#### Preparing the HTML output\n\nWhat I have so far is the raw HTML of every block, now I just have to wrap it into a useable HTML document. And for this I'll want a template approach. The template must have both `\u003c!-- TITLE --\u003e` and `\u003c!-- BODY --\u003e` for it to be useable. If a template is not provided, it will just fall back onto a minimal, default one.\n\n```nim insert weaved into html template\nproc intoHtmlTemplate(weaved: string, inputTemplate: string = \"\", title: string = \"\"): string =\n  const defaultTemp = staticRead(\"default.html\")\n\n  let temp = (\n    if inputTemplate.strip() == \"\": defaultTemp\n    else: inputTemplate\n  )\n\n  # \u003c!-- TITLE --\u003e is replaced with the source file name.\n  # \u003c!-- BODY --\u003e is replaced with the body of the document.\n  # The spellings need to exact.\n\n  return temp.replace(\"\u003c!-- TITLE --\u003e\", title).replace(\"\u003c!-- BODY --\u003e\", weaved)\n```\n\nThis is the default HTML template, you can find it in the source under `src/default.html`. For styling, it assumes a `css/screen.css` and `css/print.css` to be available from the point of view of the rendered HTML file.\n\n```html /src/default.html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003ctitle\u003e\u003c!-- TITLE --\u003e\u003c/title\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003clink rel=\"stylesheet\" href=\"css/screen.css\" media=\"screen,projection,tv\"\u003e\n    \u003clink rel=\"stylesheet\" href=\"css/print.css\" media=\"print\"\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c!-- BODY --\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Tangle\n\nMeanwhile, `tangle` here exports files from the literate program to make source code that can be compiled.\n\n\n```nim call tangle command\nblocks.tangle(($args[\"\u003cdestdir/\u003e\"]))\nquit(0)\n```\n\n#### The tangle function\n\nThis tangle function needs to do two things:\n\n1. Replace code block references with the actual code blocks.\n2. Save code blocks to files when it's warranted to do so.\n\n```nim tangle function\nproc tangle(blocks: seq[Block], dest: string) =\n  var codeBlkMap: Table[string, string]\n\n  @{helper function to replace references with content}\n\n  @{fill code block mappings}\n  @{modify code block mappings with actual values}\n  @{save code block to files}\n```\n\n#### Replacing code block references\n\nFirst I'll want to go through every code block in document order and populate the code block mappings with the contents of their respective code blocks verbatim. There's no \"append\" feature, but there is a \"replace\" \"feature\" (no special syntax required), which will warn you when you're replacing a block.\n\n```nim fill code block mappings\nfor txblock in blocks:\n  case txblock.kind\n  of Code:\n    if txblock.name.len \u003c 1: continue\n    if codeBlkMap.hasKey txblock.name:\n      stderr.writeLine \"WARNING: replacing code block \" \u0026 txblock.name\n    codeBlkMap[txblock.name] = txblock.content\n  of Prose:\n    discard\n```\n\nThen I'll go through the code block mappings again to replace the references with the actual content. Er, uh… this should probably be done recursively, but for small code stuff I think it works alright for now.\n\n```nim modify code block mappings with actual values\nfor codeBlk in codeBlkMap.mvalues: # :(\n  for _ in 0 .. codeBlk.findAll(codeBlockRefSpacesPtn).len: # :(\n    codeBlk = codeBlk.replace(codeBlockRefSpacesPtn, replaceReferencesWithContent)\n```\nThe references are replaced in such a way that it retains the leading spaces used for the reference in every line of the replacement. For example, if a reference `@{something}` starts with 4 spaces, the entire thing to replace it will start every line with an additional 4 spaces. I think this can help in whitespace-sensitive languages by ensuring you don't accidentally change the indentation inside of a loop or something.\n\n```nim helper function to replace references with content\nproc replaceReferencesWithContent(m: RegexMatch2, s: string): string =\n  let keyName = s[m.group(1)]\n\n  if codeBlkMap.hasKey keyName:\n    # indent each line with the same amount of spaces as\n    # the indentation of the references\n    let initialNLAndSpaces = s[m.group(0)]\n    if (\n      let initialSpaces = initialNLAndSpaces.replace(\"\\n\", \"\")\n      initialSpaces.len \u003e 0\n    ):\n      var\n        paddedCodeLines = initialSpaces\n        isInitialLine = true\n      for line in codeBlkMap[keyName].strip().splitLines():\n        if isInitialLine:\n          paddedCodeLines \u0026= line \u0026 \"\\n\"\n          isInitialLine = false\n        else:\n          paddedCodeLines \u0026= initialSpaces \u0026 line \u0026 '\\n'\n      return paddedCodeLines\n    return initialNLAndSpaces \u0026 codeBlkMap[keyName]\n\n  stderr.writeLine \"WARNING: key \" \u0026 keyName \u0026 \" not found!\"\n  return \"\"\n```\n\n#### Saving to files\n\nNailIt will only save to files code blocks which start with a `/`. The `/` here means \"your current working directory or your specified user directory.\"\n\n```nim save code block to files\nfor key in codeBlkMap.keys:\n  if key.len \u003e 0 and key[0] == '/':\n    let outFileName = [dest, key[1 ..^ 1]].join($os.DirSep)\n    outFileName.parentDir.createDir()\n    outFileName.open(fmWrite).write(codeBlkMap[key])\n    stderr.writeLine \"INFO: wrote to file \" \u0026 outFileName.string\n```\n\n### View Blocks\n\nThis `blocks` command is really just a debugging tool. It answers the question of \"What does NailIt actually see when I give it my literate program?\"\n\n```nim call blocks command\nblocks.displayBlocks()\n```\n\n```nim blocks function\nproc displayBlocks(blocks: seq[Block]) =\n  var num = 1\n  for b in blocks:\n    let blockTitle =\n      \"Block \" \u0026 (\n        case b.kind\n        of Prose: \"P.\"\n        of Code: \"C.\"\n      ) \u0026 $num \u0026 (\n        case b.kind\n        of Prose: \"\"\n        of Code: \" \\\"\" \u0026 b.name \u0026 \"\\\" (\" \u0026 b.language \u0026 \")\"\n      )\n    echo '-'.repeat(blockTitle.len)\n    echo blockTitle\n    echo '-'.repeat(blockTitle.len)\n    num += 1\n    echo b.content\n    echo '-'.repeat(blockTitle.len) \u0026 '\\n'\n```\n\n### Overall program structure\n\nFinally, let's put this all together into the full code for the thing.\n\n```nim /src/nailit.nim\n@{imports}\n@{types}\n@{constants}\n@{functions}\n\nwhen is_main_module:\n  @{main program}\n```\n\n```nim imports\nimport regex\nimport std/[strutils, tables, strtabs, os]\nimport packages/docutils/[rst, rstgen]\nimport docopt\n```\n\n```nim types\n@{block type definition}\n```\n\n```nim constants\n@{regex patterns}\n```\n\n```nim functions\n@{function to normalize labels}\n\n@{function to determine if a regex match is empty}\n\n@{get blocks from source function}\n\n@{weave function}\n\n@{tangle function}\n\n@{insert weaved into html template}\n\n@{blocks function}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoomten%2Fnailit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzoomten%2Fnailit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoomten%2Fnailit/lists"}