From 4cf3a955545b89c4fce83509968a14265098f51b Mon Sep 17 00:00:00 2001 From: Constantine Molchanov Date: Thu, 3 Sep 2026 15:45:58 +0400 Subject: [PATCH] Feature: `nim book` command to produce documentation from Nim-flavored Markdown (#26139) This PR adds a new Nim compiler command and introduces some improvements to the docgen suite in general. 1. Adds `nim book`, the new command that takes a directory with Markdown/ReST files and generates a navigatable, searchable, Nim-first documentation site. 2. Refactors the default nimdoc.cfg, specifically the part marked with "needs to be refactored." Code duplication was removed, new overridable variables were added, quirky logic with the "Group by" switch display was fixed. Here's a live demo of a `nim book` produced book: https://moigagoo.github.io/nim-chronos/ The original mdBook-powered version: https://status-im.github.io/nim-chronos/ Related to this PR but valuable on their own: 1. `.. include::` directive has received several improvements: - You can now include code from line to line, merged: https://github.com/nim-lang/Nim/pull/26130 - You can now include code with syntax highlighting, merged: https://github.com/nim-lang/Nim/pull/26146 2. `.. admonition::` directive (and its derivatives like `warning`, `error`, etc.) got new useful functions: - You can now set a title to your admonitions, open: https://github.com/nim-lang/Nim/pull/26159 - You can make admonitions collapsible (useful when you need to include a large chunk if code), open: https://github.com/nim-lang/Nim/pull/26159 --- compiler/commands.nim | 1 + compiler/docgen.nim | 176 ++++++++++++++++-- compiler/main.nim | 8 +- compiler/options.nim | 1 + config/nimdoc.cfg | 118 +++++------- doc/docgen.md | 103 +++++++++- doc/markdown_rst.md | 27 +-- doc/mdbookmigration.prompt | 167 +++++++++++++++++ doc/nimdoc.css | 9 + koch.nim | 1 + lib/packages/docutils/rst.nim | 7 +- lib/packages/docutils/rstast.nim | 6 + lib/packages/docutils/rstgen.nim | 45 +++-- lib/packages/docutils/rstidx.nim | 2 +- nimdoc/bookproject/SUMMARY.md | 18 ++ nimdoc/bookproject/code1.nim | 2 + nimdoc/bookproject/code2.nim | 10 + nimdoc/bookproject/code3.py | 3 + nimdoc/bookproject/expected/api/code1.html | 114 ++++++++++++ nimdoc/bookproject/expected/api/code1.idx | 2 + nimdoc/bookproject/expected/api/theindex.html | 46 +++++ nimdoc/bookproject/expected/intro.html | 127 +++++++++++++ nimdoc/bookproject/expected/intro.idx | 4 + nimdoc/bookproject/expected/page1.html | 81 ++++++++ nimdoc/bookproject/expected/page1.idx | 2 + .../expected/sections/1/intro.html | 81 ++++++++ .../bookproject/expected/sections/1/intro.idx | 2 + .../expected/sections/1/page2.html | 80 ++++++++ .../bookproject/expected/sections/1/page2.idx | 2 + .../expected/sections/1/page2/subpage1.html | 80 ++++++++ .../expected/sections/1/page2/subpage1.idx | 2 + .../expected/sections/2/intro.html | 80 ++++++++ .../bookproject/expected/sections/2/intro.idx | 2 + .../expected/sections/2/page3.html | 80 ++++++++ .../bookproject/expected/sections/2/page3.idx | 2 + .../expected/sections/2/page3/subpage2.html | 80 ++++++++ .../expected/sections/2/page3/subpage2.idx | 2 + nimdoc/bookproject/expected/theindex.html | 82 ++++++++ nimdoc/bookproject/intro.md | 85 +++++++++ nimdoc/bookproject/page1.md | 5 + nimdoc/bookproject/sections/1/intro.md | 5 + nimdoc/bookproject/sections/1/page2.md | 1 + .../bookproject/sections/1/page2/subpage1.md | 1 + nimdoc/bookproject/sections/2/intro.md | 1 + nimdoc/bookproject/sections/2/page3.md | 1 + .../bookproject/sections/2/page3/subpage2.md | 1 + nimdoc/booktester.nim | 53 ++++++ .../extlinks/project/expected/_._/util.html | 48 ++--- nimdoc/extlinks/project/expected/main.html | 48 ++--- .../project/expected/sub/submodule.html | 48 ++--- nimdoc/rst2html/expected/rst_examples.html | 41 ++-- .../test_doctype/expected/test_doctype.html | 48 ++--- .../expected/index.html | 48 ++--- nimdoc/testproject/expected/nimdoc.out.css | 9 + .../expected/subdir/subdir_b/utils.html | 48 ++--- nimdoc/testproject/expected/testproject.html | 48 ++--- tests/stdlib/trstgen.nim | 30 +++ 57 files changed, 1948 insertions(+), 276 deletions(-) create mode 100644 doc/mdbookmigration.prompt create mode 100644 nimdoc/bookproject/SUMMARY.md create mode 100644 nimdoc/bookproject/code1.nim create mode 100644 nimdoc/bookproject/code2.nim create mode 100644 nimdoc/bookproject/code3.py create mode 100644 nimdoc/bookproject/expected/api/code1.html create mode 100644 nimdoc/bookproject/expected/api/code1.idx create mode 100644 nimdoc/bookproject/expected/api/theindex.html create mode 100644 nimdoc/bookproject/expected/intro.html create mode 100644 nimdoc/bookproject/expected/intro.idx create mode 100644 nimdoc/bookproject/expected/page1.html create mode 100644 nimdoc/bookproject/expected/page1.idx create mode 100644 nimdoc/bookproject/expected/sections/1/intro.html create mode 100644 nimdoc/bookproject/expected/sections/1/intro.idx create mode 100644 nimdoc/bookproject/expected/sections/1/page2.html create mode 100644 nimdoc/bookproject/expected/sections/1/page2.idx create mode 100644 nimdoc/bookproject/expected/sections/1/page2/subpage1.html create mode 100644 nimdoc/bookproject/expected/sections/1/page2/subpage1.idx create mode 100644 nimdoc/bookproject/expected/sections/2/intro.html create mode 100644 nimdoc/bookproject/expected/sections/2/intro.idx create mode 100644 nimdoc/bookproject/expected/sections/2/page3.html create mode 100644 nimdoc/bookproject/expected/sections/2/page3.idx create mode 100644 nimdoc/bookproject/expected/sections/2/page3/subpage2.html create mode 100644 nimdoc/bookproject/expected/sections/2/page3/subpage2.idx create mode 100644 nimdoc/bookproject/expected/theindex.html create mode 100644 nimdoc/bookproject/intro.md create mode 100644 nimdoc/bookproject/page1.md create mode 100644 nimdoc/bookproject/sections/1/intro.md create mode 100644 nimdoc/bookproject/sections/1/page2.md create mode 100644 nimdoc/bookproject/sections/1/page2/subpage1.md create mode 100644 nimdoc/bookproject/sections/2/intro.md create mode 100644 nimdoc/bookproject/sections/2/page3.md create mode 100644 nimdoc/bookproject/sections/2/page3/subpage2.md create mode 100644 nimdoc/booktester.nim diff --git a/compiler/commands.nim b/compiler/commands.nim index 0a045c2918..6cce04404a 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -491,6 +491,7 @@ proc parseCommand*(command: string): Command = of "e": cmdNimscript of "doc0": cmdDoc0 of "doc2", "doc": cmdDoc + of "book": cmdBook of "doc2tex": cmdDoc2tex of "rst2html": cmdRst2html of "md2tex": cmdMd2tex diff --git a/compiler/docgen.nim b/compiler/docgen.nim index e009d06f22..5d082c3a70 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -24,7 +24,7 @@ import import packages/docutils/rstast except FileIndex, TLineInfo import std/[os, strutils, strtabs, algorithm, json, osproc, tables, intsets, xmltree, sequtils] -from std/uri import encodeUrl +from std/uri import encodeUrl, parseUri, isAbsolute from nodejs import findNodeJs when defined(nimPreviewSlimSystem): @@ -76,6 +76,19 @@ type json: JsonNode rst: PRstNode rstField: string + NavItemKind = enum + niLink + niLabel + niHeading + NavItem = object + ## Navigation entry for the sidebar navigation. + title: string + case kind: NavItemKind + of niLink: + dest: string + else: + discard + sons: seq[NavItem] TDocumentor = object of rstgen.RstGenerator modDescPre: ItemPre # module description, not finalized modDescFinal: string # module description, after RST pass 2 and rendering @@ -1726,10 +1739,19 @@ proc genOutFile(d: PDoc, groupedToc = false): string = dispA(d.conf, subtitle, "

$1

", "\\\\\\vspace{0.5em}\\large $1", [esc(d.target, d.meta[metaSubtitle])]) + let theIndexHref = relLink(d.conf.outDir, d.destFile.AbsoluteFile, theindexFname.RelativeFile) + let indexLink = getConfigVar(d.conf, "doc.body_toc_indexlink") % ["theindexhref", theIndexHref] + let navLinks = getConfigVar(d.conf, "doc.body_toc_navlinks", "") + let globalLinks = getConfigVar(d.conf, "doc.body_toc_globallinks") % [ + "body_toc_navlinks", navLinks, + "body_toc_indexlink", indexLink, + "theindexhref", theIndexHref] # added because the `boot` branch uses `$theindexhref` directly + let searchBox = getConfigVar(d.conf, "doc.body_toc_searchbox") + let themeSelect = getConfigVar(d.conf, "doc.body_toc_themeselect") var groupsection = getConfigVar(d.conf, "doc.body_toc_groupsection") - let bodyname = if d.hasToc and not d.standaloneDoc and not d.conf.isLatexCmd: + let bodyname = if d.hasToc and d.standaloneDoc and not d.conf.isLatexCmd: groupsection.setLen 0 - "doc.body_toc_group" + "doc.body_toc" elif d.hasToc: "doc.body_toc" else: "doc.body_no_toc" let seeSrc = genSeeSrc(d, d.filename, 1) @@ -1738,9 +1760,11 @@ proc genOutFile(d: PDoc, groupedToc = false): string = "tableofcontents", toc, "moduledesc", d.modDescFinal, "date", getDateStr(), "time", getClockStr(), "content", code, "deprecationMsg", d.modDeprecationMsg, - "theindexhref", relLink(d.conf.outDir, d.destFile.AbsoluteFile, - theindexFname.RelativeFile), - "body_toc_groupsection", groupsection, "seeSrc", seeSrc] + "body_toc_groupsection", groupsection, + "body_toc_globallinks", globalLinks, + "body_toc_searchbox", searchBox, + "body_toc_themeselect", themeSelect, + "seeSrc", seeSrc] if optCompileOnly notin d.conf.globalOptions: # XXX what is this hack doing here? 'optCompileOnly' means raw output!? code = getConfigVar(d.conf, "doc.file") % [ @@ -1852,10 +1876,15 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename: AbsoluteFile, outExt: string, - preferMarkdown: bool) = - var filen = addFileExt(filename, "txt") + preferMarkdown: bool, hasToc=false, addTxtExt=true) = + let filen = + if addTxtExt: + filename + else: + addFileExt(filename, "txt") + var d = newDocumentor(filen, cache, conf, outExt, standaloneDoc = true, - preferMarkdown = preferMarkdown, hasToc = false) + preferMarkdown = preferMarkdown, hasToc = hasToc) try: let rst = parseRst(readFile(filen.string), line=LineRstInit, column=ColRstInit, @@ -1926,10 +1955,11 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) = except IOError: rawMessage(conf, errCannotOpenFile, filename.string) -proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"") = +proc commandBuildIndex*(conf: ConfigRef, dir: string, outFile = RelativeFile"", + exclCode = false, inclHeaders = false) = if optGenIndexOnly in conf.globalOptions: return - var content = mergeIndexes(dir) + var content = mergeIndexes(dir, exclCode, inclHeaders) var outFile = outFile if outFile.isEmpty: outFile = theindexFname.RelativeFile.changeFileExt("") @@ -1962,3 +1992,127 @@ proc commandBuildIndexJson*(conf: ConfigRef, dir: string, outFile = RelativeFile writeFile(filename, $body) except IOError: rawMessage(conf, errCannotOpenFile, filename.string) + +proc commandBook*(cache: IdentCache, conf: ConfigRef) = + let bookDir = conf.projectFull.string + conf.projectPath = AbsoluteDir(bookDir) # set bookDir to be the documentation root, + # so that we don't end up with our output in `/`; + # we want it in `` + let summaryFilePath = bookDir / "SUMMARY.md" + if not fileExists(summaryFilePath): + rawMessage(conf, errCannotOpenFile, summaryFilePath) + return + let summaryFile = AbsoluteFile(summaryFilePath) + var d = newDocumentor(summaryFile, cache, conf, HtmlExt, + standaloneDoc = true, preferMarkdown = true, hasToc = true) + let rst = parseRst(readFile(summaryFile.string), + line=LineRstInit, column=ColRstInit, conf, d.sharedState) + var navTree: seq[NavItem] = @[] + + proc traverseBulletList(list: PRstNode): seq[NavItem] = + ## Recursively go through a bullet list and generate a nav subtree from it. + result = @[] + for node in list.sons: + let inner = node.sons[0] + let innerBody = inner.sons[0] + var item = + case innerBody.kind + of rnHyperlink: + NavItem(title: innerBody.sons[0].text, kind: niLink, dest:innerBody.sons[1].text) + of rnLeaf: + NavItem(title: inner.renderRstToText(), kind: niLabel) + else: + NavItem() + + if len(node.sons) > 1: + item.sons = traverseBulletList(node.sons[1]) + + result.add(item) + + proc parseSummary(root: PRstNode): seq[NavItem] = + ## Parse the root node from the summary file and generate the nav tree. + result = @[] + let nodes = if root.kind == rnInner: root.sons else: @[root] + for node in nodes: + case node.kind + of rnMarkdownHeadline: + let title = node.renderRstToText() + result.add(NavItem(title: title, kind: niHeading)) + of rnBulletList: + result &= traverseBulletList(node) + else: + discard + + proc isGlobalUri(path: string): bool = + parseUri(path).isAbsolute() + + proc existsSrcFile(path: string): bool = + not path.isGlobalUri and fileExists(bookDir / path) + + proc generateNavLinks(navSubTree: seq[NavItem], destFile: AbsoluteFile, + nested=false): tuple[navLinks: string, hasCurrentPage: bool] = + ## Generate the navigation links for the sidebar. + ## Each page has a different set of those, adjusted for relative location. + let tocClassName = + if nested: "nested-toc-section" + else: "simple-toc-section" + var navLinks = """
    """ % [tocClassName] + var containsCurrent = false + for item in navSubTree: + var isCurrent = false + let content = + case item.kind + of niHeading: + """$#""" % [esc(outHtml, item.title)] + of niLabel: + esc(outHtml, item.title) + of niLink: + let href = + if item.dest.existsSrcFile(): + relLink(conf.outDir, destFile, RelativeFile(item.dest.changeFileExt(HtmlExt))) + else: + item.dest + isCurrent = + not item.dest.isGlobalUri() and + destFile == getOutFile2(conf, presentationPath(conf, AbsoluteFile(bookDir / item.dest)), HtmlExt, false) + let cls = + if isCurrent: "current" + else: "" + """$#""" % [href, cls, esc(outHtml, item.title)] + if len(item.sons) == 0: + navLinks &= """
  • $#
  • """ % [content] + else: + let (sonsNavLinks, sonsContainCurrent) = generateNavLinks(item.sons, destFile, nested=true) + let unfold = isCurrent or sonsContainCurrent + let openAttr = if unfold: " open" else: "" + navLinks &= """
  • $#$#
  • """ % + [openAttr, content, sonsNavLinks] + containsCurrent = containsCurrent or unfold + containsCurrent = containsCurrent or isCurrent + navLinks &= """
""" + result = (navLinks, containsCurrent) + + proc generatePage(filename: AbsoluteFile) = + ## Generate an HTML page from a Markdown file. + conf.outFile = RelativeFile"" # reset to force path re-generation for each page + let destFile = getOutFile2(conf, presentationPath(conf, filename), HtmlExt, false) + let (navLinks, _) = generateNavLinks(navTree, destFile) + setConfigVar(conf, "doc.body_toc_navlinks", navLinks) + commandRstAux(cache, conf, filename, HtmlExt, + preferMarkdown=true, hasToc=true, addTxtExt=false) + + proc generatePages(navSubTree: seq[NavItem]) = + ## Generate all pages from the Markdown files listed in the summary file. + for item in navSubTree: + if item.kind == niLink and not item.dest.isGlobalUri(): + let pageFilePath = bookDir / item.dest + if fileExists(pageFilePath): + let pageFile = AbsoluteFile(pageFilePath) + generatePage(pageFile) + else: + rawMessage(conf, warnCannotOpenFile, pageFilePath) + generatePages(item.sons) + + setConfigVar(conf, "doc.body_toc_groupsection", "") # we don't need "Group by" section in standalone docs + navTree = parseSummary(rst) + generatePages(navTree) diff --git a/compiler/main.nim b/compiler/main.nim index b589cd62de..9a908721f4 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -313,6 +313,7 @@ proc mainCommand*(graph: ModuleGraph) = ## command prepass if conf.cmd == cmdCrun: conf.globalOptions.incl {optRun, optUseNimcache} + if conf.cmd == cmdBook: conf.globalOptions.incl {optGenIndex} if conf.cmd notin cmdBackends + {cmdTcc}: customizeForBackend(backendC) if conf.outDir.isEmpty: # doc like commands can generate a lot of files (especially with --project) @@ -321,7 +322,7 @@ proc mainCommand*(graph: ModuleGraph) = else: conf.projectPath if not ret.string.isAbsolute: # `AbsoluteDir` is not a real guarantee rawMessage(conf, errCannotOpenFile, ret.string & "/") - if conf.cmd in cmdDocLike + {cmdRst2html, cmdRst2tex, cmdMd2html, cmdMd2tex}: + if conf.cmd in cmdDocLike + {cmdRst2html, cmdRst2tex, cmdMd2html, cmdMd2tex, cmdBook}: ret = ret / htmldocsDir conf.outDir = ret @@ -348,6 +349,11 @@ proc mainCommand*(graph: ModuleGraph) = commandDoc2(graph, HtmlExt) if optGenIndex in conf.globalOptions and optWholeProject in conf.globalOptions: commandBuildIndex(conf, $conf.outDir) + of cmdBook: + loadConfigs(DocConfig, cache, conf, graph.idgen) + conf.setNoteDefaults(warnCannotOpenFile, true) + commandBook(cache, conf) + commandBuildIndex(conf, $conf.outDir, exclCode = true, inclHeaders = true) of cmdRst2html, cmdMd2html: # XXX: why are warnings disabled by default for rst2html and rst2tex? for warn in rstWarnings: diff --git a/compiler/options.nim b/compiler/options.nim index e3107920a6..e2e05d227f 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -197,6 +197,7 @@ type cmdNimscript # evaluate nimscript cmdDoc0 cmdDoc # convert .nim doc comments to HTML + cmdBook # generate documentation site from a directory with Markdown files cmdDoc2tex # convert .nim doc comments to LaTeX cmdRst2html # convert a reStructuredText file to HTML cmdRst2tex # convert a reStructuredText file to TeX diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg index 4f608bc4f1..d282826ee1 100644 --- a/config/nimdoc.cfg +++ b/config/nimdoc.cfg @@ -127,36 +127,57 @@ doc.body_toc_groupsection = """ """ +doc.body_toc_themeselect = """ +
+ + +
+""" + +doc.body_toc_indexlink = """ + +""" + +doc.body_toc_searchbox = """ +
+ Search: +
+""" + @if boot: -# This is enabled with the "boot" directive to generate -# the compiler documentation. -# As a user, tweak the block below instead. -# You can add your own global-links entries -doc.body_toc_group = """ +doc.body_toc_globallinks = """ + +""" +@else: +doc.body_toc_globallinks= """ + +""" +@end + +doc.body_toc = """
-
- - -
- -
- Search: -
+ $body_toc_themeselect + $body_toc_globallinks + $body_toc_searchbox $body_toc_groupsection $tableofcontents
@@ -170,49 +191,6 @@ doc.body_toc_group = """
""" -@else -# keep in sink with other `doc.body_toc_group` or better, refactor -doc.body_toc_group = """ -
-
-
- - -
- -
- Search: -
-
- Group by: - -
- $tableofcontents -
-
- $seeSrc -
- $deprecationMsg -

$moduledesc

- $content -
-
-""" -@end - -doc.body_toc %= "${doc.body_toc_group}" # should only be used for boot - doc.body_no_toc = """ $moduledesc $content diff --git a/doc/docgen.md b/doc/docgen.md index 52d855e6a9..2cab47c940 100644 --- a/doc/docgen.md +++ b/doc/docgen.md @@ -25,6 +25,7 @@ exported symbols (`*`), including procedures, types, and variables. command output format =================== ============== `nim doc`:cmd: ``.html`` HTML +`nim book`:cmd: ``.html`` HTML `nim doc2tex`:cmd: ``.tex`` LaTeX `nim jsondoc`:cmd: ``.json`` JSON =================== ============== @@ -36,6 +37,8 @@ See [Nim-flavored Markdown and reStructuredText] document for the description of this feature and particularly section [Command line usage] for the full list of supported commands. +You can also generate a full documentation site from a directory with [Nim-flavored Markdown and reStructuredText] files and `SUMMARY.md` using `nim book`:cmd:, similarly to how [mdBook](https://rust-lang.github.io/mdBook/) and other doc generators work. + Quick start ----------- @@ -62,6 +65,12 @@ Generate HTML documentation for a whole project: # Use `--showNonExports` to show non-exported fields of an exported type. ``` +Generate a site from a directory of Markdown files and `SUMMARY.md`: + + ```cmd + nim book + ``` + Documentation Comments ---------------------- @@ -179,6 +188,98 @@ Index (``.idx``) files are used for 2 different purposes: see [Buildindex command]. +Book Generator +============== + +Nim ships with a complete book generator that lets you build sites from Markdown/reST files organized in folders. + +Powered by `nim doc`:cmd:, `nim book`:cmd: has all its features like built-in admonitions, code inclusion syntax, and checked Nim code references, but also offers `SUMMARY.md`-based source collection and navigation generation that should be familiar to [mdBook](https://rust-lang.github.io/mdBook/) users and simplify migration. + +Quickstart +---------- + +1. Create a directory for the book source, e.g `bookSrc`. +2. Put a Markdown file in this directory, e.g. `welcome.md`: + + ```markdown + # Welcome to Nim Book + + This is the first paragraph. + ``` + +3. Put a file called `SUMMARY.md` in the same directory. The file lists all your pages (one so far): + + ```markdown + - [Welcome](./welcome.md) + ``` + +4. Run `nim book`:cmd: with the book source directory: + + ```cmd + $ nim book boorSrc + ``` + +The docs will be generated in `htmldocs`, which is the default for `nim docs`:cmd:. You can customize the location with `--outDir:OUTDIR`:option:, just like for `nim doc`:cmd:. + +To see your book in the browser, serve `htmldocs` with any static server (e.g. `python3 -m http.server -d htmldocs/` ) and open the localhost location that it provides (e.g. http://localhost:8000). This way you'll have search working. + +To set the title for the page, use ``.. title::``: + + ```markdown + .. title:: Welcome to Nim Book + + # Part 1 + + This is the first paragraph. + ``` + +To customize the way your page is listed in the sidebar navigation, set its link title in `SUMMARY.md`: + + ```markdown + - [Intro](./welcome.md) + ``` + +As your `SUMMARY.md` grows, you'll want to group the pages into sections. You can do that using nested bullet lists and headers: + + ```markdown + - [Intro](./welcome.md) + - [Guides](./guides.md) + - [Dev Guide](./guides/dev.md) + - [User Guide](./guides/user.md) + + # References + + - accounts + - [Intro](./ref/accounts/intro.md) + - [Glossary](./ref/accounts/glossary.md) + - billing + - [Intro](./ref/billing/intro.md) + - [Glossary](./ref/billing/glossary.md) + ``` + +Migrating from mdBook +--------------------- + +The easiest way to migrate to `nim book`:cmd: from mdBook is to feed the pre-cooked prompt to an LLM of your choice. + +.. note:: + :title: Migrate from mdBook prompt + :collapsible: closed + + .. include:: ./mdbookmigration.prompt + :literal: + +Or use the same prompt as a checklist to do the migration manually. + +Notes +----- + +- if your docs won't build because an ``.idx`` file cannot be found, pre-build the indexes with `nim book --index:only bookSrc`:cmd: and then run `nim book bookSrc`:cmd: again +- `nim book`:cmd: doesn't automatically copy assets from the source directory to the destination directory. The recommended pattern to work with images and other assets is to place them in a specialized directory (i.e. `bookSrc/img`) and copy it into `htmldocs/img` after build +- for your readers' convenience, you can make a certain page your welcome page, i.e. opened by default when the reader opens your book; to do that, simply copy the desired page to ``index.html`` after build: `cp htmldocs/welcome.html htmldocs/index.html` +- to add links to page source reading and editing, use the standard `--git.url`:option: and `--git.commit`:option: options: `nim book --git.url:https://github.com/owner/repo --git.commit:master bookSrc`:cmd: + + Document Types ============== @@ -845,7 +946,7 @@ rstgen.html#setIndexTerm,RstGenerator,string,string,string,string,string) and `writeIndexFile() `_ procs. The purpose of `idx` files is to hold the interesting symbols and their HTML references so they can be later concatenated into a big index file with -[mergeIndexes()](rstgen.html#mergeIndexes,string). This section documents +[mergeIndexes()](rstgen.html#mergeIndexes,string,bool,bool). This section documents the file format in detail. Index files are line-oriented and tab-separated (newline and tab characters diff --git a/doc/markdown_rst.md b/doc/markdown_rst.md index f8d0012e55..daa98c0a66 100644 --- a/doc/markdown_rst.md +++ b/doc/markdown_rst.md @@ -42,17 +42,18 @@ the result to HTML [^html] or Latex [^latex]. Full list of supported commands: -=================== ====================== ============ ============== -command runs on... input format output format -=================== ====================== ============ ============== -`nim md2html`:cmd: standalone md files ``.md`` ``.html`` HTML -`nim md2tex`:cmd: same same ``.tex`` LaTeX -`nim rst2html`:cmd: standalone rst files ``.rst`` ``.html`` HTML -`nim rst2tex`:cmd: same same ``.tex`` LaTeX -`nim doc`:cmd: documentation comments ``.nim`` ``.html`` HTML -`nim doc2tex`:cmd: same same ``.tex`` LaTeX -`nim jsondoc`:cmd: same same ``.json`` JSON -=================== ====================== ============ ============== +=================== ======================= ============ ============== +command runs on... input format output format +=================== ======================= ============ ============== +`nim md2html`:cmd: standalone md files ``.md`` ``.html`` HTML +`nim book`:cmd: directory with md files same same +`nim md2tex`:cmd: same same ``.tex`` LaTeX +`nim rst2html`:cmd: standalone rst files ``.rst`` ``.html`` HTML +`nim rst2tex`:cmd: same same ``.tex`` LaTeX +`nim doc`:cmd: documentation comments ``.nim`` ``.html`` HTML +`nim doc2tex`:cmd: same same ``.tex`` LaTeX +`nim jsondoc`:cmd: same same ``.json`` JSON +=================== ======================= ============ ============== Basic markup @@ -95,6 +96,10 @@ Supported common RST/Markdown features: - ``include`` - admonitions: "attention", "caution", "danger", "error", "hint", "important", "note", "tip", "warning", "admonition" + - ``:title:`` option sets custom title for an admonition, + otherwise its kind capitalized is used + - ``:collapsible:`` makes an admonition collapsible in HTML output + ``:collapsible: closed`` makes it closed by default - substitution definitions: `replace` and `image` + comments * inline markup diff --git a/doc/mdbookmigration.prompt b/doc/mdbookmigration.prompt new file mode 100644 index 0000000000..4c696af431 --- /dev/null +++ b/doc/mdbookmigration.prompt @@ -0,0 +1,167 @@ +# Migrate an mdBook project to `nim book` + +Convert a Markdown documentation book (currently built with mdBook) into a +`nim book` project. The book lives in a directory (call it `book/`) with a +`SUMMARY.md` at its root, and the API docs (generated from `.nim` source via +`nim doc --project --index:on`) live in a sibling `api/` directory that ends up +at `/api/`. + +## Context + +`nim book` is a Nim compiler command that turns a directory of Nim-flavored +Markdown (`.md`) files into a static documentation site, using `SUMMARY.md` for +structure/navigation. It supports: + +- `.. include::` with `:code:` (syntax-highlighted code inclusion), + `:start-after:`/`:end-before:` (selective inclusion), and `:literal:` (plain). +- `.. admonition::` (and the shorthand `.. note::`, `.. warning::`, + `.. important::`). +- `.. title::` for page titles. +- `.. importdoc::` + Pandoc-style references for cross-referencing Nim symbols. +- `.. image::` for images. + +## Migration steps + +### 1. Admonitions + +Replace mdBook's fenced-code admonitions with RST directives: + +- ` ```admonish warning` / ` ```admonition warning` → `.. warning::` +- ` ```admonish note` / ` ```admonition note` → `.. note::` +- ` ```admonish important` → `.. important::` +- ` ```admonish info` → `.. note::` (there is **no** `info` directive; map it + to `note`) + +Valid admonition directives in Nim's RST are: `admonition`, `attention`, +`caution`, `danger`, `error`, `hint`, `important`, `note`, `tip`, `warning`. +Map any mdBook admonition type not in this list to the closest valid one +(e.g. `info` → `note`). + +The body text must be indented (3 spaces) under the directive. Remove the +surrounding ` ``` ` fences. + +### 2. Code inclusion + +Replace mdBook's `{{#include PATH}}` and `{{#shiftinclude auto:PATH:NAME}}` with +`.. include::`: + +- Whole file: `{{#include PATH}}` or `{{#shiftinclude auto:PATH:all}}` → + + ``` + .. include:: PATH + :code: + ``` + +- Selective (named section): `{{#shiftinclude auto:PATH:NAME}}` → + + ``` + .. include:: PATH + :start-after: #ANCHOR: NAME + :end-before: #ANCHOR_END: NAME + :code: + ``` + +Notes: + +- `:code:` gives Nim syntax highlighting (defaults to Nim; use `:code: ` + for other languages). +- The `#ANCHOR:` / `#ANCHOR_END:` markers must exist in the source `.nim` + files. Normalize them to `#ANCHOR:` (no space after `#`) — a space after `#` + is interpreted as a Markdown heading. +- Include paths are resolved **relative to each `.md` file's own directory** + (not the book root). Adjust `../` counts accordingly. +- Remove the surrounding ` ```nim ` fences — `.. include::` is a block + directive, not inline. + +### 3. Remove mdBook artifacts + +Remove any leftover mdBook-specific markup, in particular `` +comments (mdBook's table-of-contents placeholder). `nim book` generates its own +TOC from the document headings, so these placeholders are dead and should be +deleted. + +### 4. Emphasis syntax + +Replace underscore emphasis with asterisks: `_italic_` → `*italic*` (and +`__bold__` → `**bold**` if present). Nim's Markdown dialect does not support +`_` for emphasis — only `*`. + +Be careful not to touch underscores that are part of identifiers, filenames, or +URLs (e.g. `http_server_middleware.md`, `YOUR_NTFY_TOPIC_NAME`, `#ANCHOR_END`). +Only convert genuine emphasis markup. + +### 5. Titles + +Add `.. title:: ` at the top of each document (before any other +content), using the document's first heading as the title. + +- **Use plain text only.** `.. title::` renders its argument as RST and then + HTML-escapes the result, so any markup or escapable characters produce + garbage: + - Backticks (`` ` ``) → escaped `<tt>` HTML. + - `*` and `_` → emphasis markup. + - `&`, `<`, `>`, `"` → HTML-escaped to `&`, `<`, `>`, `"`. +- So strip/replace any of these from the title. For example, + `Scaling & Finishing Touches` must become `Scaling and Finishing Touches` + (or otherwise remove the `&`), because `&` renders as `&`. +- Remove the original top-level `# Heading` (it's now redundant with + `.. title::`). +- Promote all remaining headings one level: `##` → `#`, `###` → `##`, etc. + +### 6. References + +There are **two distinct kinds** of references, with different syntax: + +**6a. Module and page links** — use **regular Markdown links** (NOT `importdoc` +references). Module/page references via `importdoc` resolve inconsistently +(e.g. `chronos` resolves but `httpagent` doesn't), so always use explicit +Markdown links: + +- Page link: `[Errors and exceptions](./error_handling.html)` +- Module link: `[httpagent](./api/chronos/apps/http/httpagent.html)` + +The `.html` path is relative to the current page's location (adjust `../` as +needed). API module pages live under `./api/chronos/...`. + +**6b. Nim code references** (symbols, procs, types, etc.) — use Pandoc-style +references, resolved via `.. importdoc::`: + +**First read these to learn the syntax:** + +- https://nim-lang.org/docs/markdown_rst.html#referencing +- https://nim-lang.org/docs/docgen.html#simple-documentation-links + +- **The reference syntax is `[Ref]`** (square brackets, no trailing + underscore). It is **not** `` `ref`_ `` and **not** `ref_`. +- The API docs live in `./api` (i.e. `<outdir>/api/`), so `importdoc` paths + must point there: `.. importdoc:: ../../api/chronos/module` (adjust `../` + count for the file's depth). +- **Unique symbols** → `[symbol]`. +- **Ambiguous/overloaded symbols** (defined in multiple modules, or multiple + overloads) → use the parenthesized signature form: + `[symbol(ParamType1, ParamType2)]`. This is the disambiguation syntax (NOT + the comma-separated complex name). +- Leave stdlib links (`nim-lang.org/docs/...`) as-is — they can't be resolved + via `importdoc`. + +### 7. Images + +Replace Markdown image syntax `![alt](path)` with `.. image:: path` (optionally +with `:alt:`). + +## Verification + +After migration, build with: + +```sh +nim book --outdir:<outdir> book +``` + +and check: + +- No broken-link warnings. +- All `importdoc` references resolve (no "cannot open ...idx" errors). +- Titles render cleanly (no escaped HTML). +- Code blocks are syntax-highlighted. +- The sidebar navigation reflects `SUMMARY.md` (with foldable sections and + current-page highlighting). diff --git a/doc/nimdoc.css b/doc/nimdoc.css index f4ac27b28d..6288d2d9cc 100644 --- a/doc/nimdoc.css +++ b/doc/nimdoc.css @@ -553,6 +553,11 @@ ul.nested-toc-section { ul.nested-toc-section > li { margin-left: 1.25em; } +ul.simple-toc-section a.current, +ul.nested-toc-section a.current { + font-weight: bold; + color: var(--anchor-focus); +} ol.arabic { list-style: decimal; } @@ -1139,3 +1144,7 @@ span.pragmawrap { span.attachedType { display: none; visibility: hidden; } + +summary { + cursor: pointer; +} diff --git a/koch.nim b/koch.nim index b8429de1cf..ecf015f0ad 100644 --- a/koch.nim +++ b/koch.nim @@ -751,6 +751,7 @@ proc runCI(cmd: string) = execFold("Run nimdoc tests", "nim r nimdoc/tester") execFold("Run rst2html tests", "nim r nimdoc/rsttester") + execFold("Run nimbook tests", "nim r nimdoc/booktester") execFold("Run nimpretty tests", "nim r nimpretty/tester.nim") when defined(posix): # refs #18385, build with -d:release instead of -d:danger for testing diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index e1d7476268..782337c6f1 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -3425,8 +3425,13 @@ proc dirIndex(p: var RstParser): PRstNode = result = parseDirective(p, rnIndex, {}, parseSectionWrapper) proc dirAdmonition(p: var RstParser, d: string): PRstNode = - result = parseDirective(p, rnAdmonition, {}, parseSectionWrapper) + result = parseDirective(p, rnAdmonition, {hasOptions}, parseSectionWrapper) result.adType = d + result.title = result.getFieldValue("title").strip() + result.collapsible = result.getFieldValue("collapsible") != "" + result.closed = result.collapsible and + result.getFieldValue("collapsible").strip() == "closed" + result.sons[1] = nil proc dirDefaultRole(p: var RstParser): PRstNode = result = parseDirective(p, rnDefaultRole, {hasArg}, nil) diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index 2bbb0d0b83..9b7628970f 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -104,6 +104,12 @@ type of rnAdmonition: adType*: string ## admonition type: "note", "caution", etc. This ## text will set the style and also be displayed + title*: string ## this text will be displayed (if given) + ## instead of the admonition type + collapsible*: bool ## if set, the admonition is rendered + ## as a collapsible block in HTML + closed*: bool ## if set, and ``collapsible`` is ``true``, + ## render the collapsible block closed by default of rnOverline, rnHeadline, rnMarkdownHeadline: level*: int ## level of headings starting from 1 (main ## chapter) to larger ones (minor sub-sections) diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 36717145ab..819c835a22 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -333,7 +333,7 @@ proc setIndexTerm*(d: var RstGenerator; k: IndexEntryKind, htmlFile, id, term: s ## The `id` will be appended with a hash character only if its length is not ## zero, otherwise no specific anchor will be generated. In general you ## should only pass an empty `id` value for the title of standalone rst - ## documents (they are special for the `mergeIndexes() <#mergeIndexes,string>`_ + ## documents (they are special for the `mergeIndexes() <#mergeIndexes,string,bool,bool>`_ ## proc, see `Index (idx) file format <docgen.html#index-idx-file-format>`_ ## for more information). Unlike other index terms, title entries are ## inserted at the beginning of the accumulated buffer to maintain a logical @@ -574,10 +574,14 @@ proc generateModuleJumps(modules: seq[string]): string = result.add(chunks.join(", ") & ".<br/>") -proc readIndexDir*(dir: string): +proc readIndexDir*(dir: string, exclCode = false, inclHeaders = false): tuple[modules: seq[string], symbols: seq[IndexEntry], docs: IndexedDocs] = ## Walks `dir` reading ``.idx`` files converting them in IndexEntry items. ## + ## If  `exclCode` is  `true`, skip  `.idx ` files for Nim modules. + ## + ## If  `inclHeaders` is  `true`, index markup headers. + ## ## Returns the list of found module names, the list of free symbol entries ## and the different documentation indexes. The list of modules is sorted. ## See the documentation of ``mergeIndexes`` for details. @@ -593,6 +597,8 @@ proc readIndexDir*(dir: string): # Depending on type add this to the list of symbols or table of APIs. if title.kind == ieNimTitle: + if exclCode: + continue for i in 0 ..< fileEntries.len: if fileEntries[i].kind != ieNim: continue @@ -613,15 +619,20 @@ proc readIndexDir*(dir: string): title.aux = "doc_toc_" & $result.docs.len result.docs[title] = fileEntries + var indexedRoles = {ieIdxRole} + + if inclHeaders: + indexedRoles.incl(ieHeading) + for i in 0 ..< fileEntries.len: - if fileEntries[i].kind != ieIdxRole: + if fileEntries[i].kind notin indexedRoles: continue setLen(result.symbols, L + 1) result.symbols[L] = fileEntries[i] inc L -proc mergeIndexes*(dir: string): string = +proc mergeIndexes*(dir: string, exclCode = false, inclHeaders = false): string = ## Merges all index files in `dir` and returns the generated index as HTML. ## ## This proc will first scan `dir` for index files with the ``.idx`` @@ -649,7 +660,7 @@ proc mergeIndexes*(dir: string): string = ## ## Returns the merged and sorted indices into a single HTML block which can ## be further embedded into nimdoc templates. - var (modules, symbols, docs) = readIndexDir(dir) + var (modules, symbols, docs) = readIndexDir(dir, exclCode, inclHeaders) sort(modules, system.cmp) result = "" @@ -1069,15 +1080,25 @@ proc renderAdmonition(d: PDoc, n: PRstNode, result: var string) = of "danger", "error": htmlCls = "admonition-error"; texSz = "\\Large"; texColor = "red" else: discard - let txt = n.adType.capitalizeAscii() + let txt = if n.title != "": n.title else: n.adType.capitalizeAscii() let htmlHead = "<div class=\"admonition " & htmlCls & "\">" - renderAux(d, n, + let htmlBody = + if n.collapsible: + if n.closed: + htmlHead & "<details><summary><span$2 class=\"" & htmlCls & "-text\"><b>" & txt & + "</b></span></summary>\n" & "$1</details></div>\n" + else: + htmlHead & "<details open><summary><span$2 class=\"" & htmlCls & "-text\"><b>" & txt & + "</b></span></summary>\n" & "$1</details></div>\n" + else: htmlHead & "<span$2 class=\"" & htmlCls & "-text\"><b>" & txt & - ":</b></span>\n" & "$1</div>\n", - "\n\n\\begin{rstadmonition}[borderline west={0.2em}{0pt}{" & - texColor & "}]$2\n" & - "{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " & - "$1\n\\end{rstadmonition}\n", + ":</b></span>\n" & "$1</div>\n" + let texBody = "\n\n\\begin{rstadmonition}[borderline west={0.2em}{0pt}{" & + texColor & "}]$2\n" & + "{" & texSz & "\\color{" & texColor & "}{\\textbf{" & txt & ":}}} " & + "$1\n\\end{rstadmonition}\n" + renderAux(d, n, + htmlBody, texBody, result) proc renderHyperlink(d: PDoc, text, link: PRstNode, result: var string, diff --git a/lib/packages/docutils/rstidx.nim b/lib/packages/docutils/rstidx.nim index 1472d28fd7..952cb3fd46 100644 --- a/lib/packages/docutils/rstidx.nim +++ b/lib/packages/docutils/rstidx.nim @@ -35,7 +35,7 @@ proc isDocumentationTitle*(hyperlink: string): bool = ## Returns true if the hyperlink is actually a documentation title. ## ## Documentation titles lack the hash. See `mergeIndexes() - ## <#mergeIndexes,string>`_ for a more detailed explanation. + ## <#mergeIndexes,string,bool,bool>`_ for a more detailed explanation. result = hyperlink.find('#') < 0 proc `$`*(e: IndexEntry): string = diff --git a/nimdoc/bookproject/SUMMARY.md b/nimdoc/bookproject/SUMMARY.md new file mode 100644 index 0000000000..1608c43f6c --- /dev/null +++ b/nimdoc/bookproject/SUMMARY.md @@ -0,0 +1,18 @@ +- [Intro](./intro.md) +- [Page 1](./page1.md) + +# Section 1 + +- [Intro](./sections/1/intro.md) +- [Page 2](./sections/1/page2.md) + - [Subpage 1](./sections/1/page2/subpage1.md) + +- Section 2 + - [Intro](./sections/2/intro.md) + - [Page 3](./sections/2/page3.md) + - [Subpage 2](./sections/2/page3/subpage2.md) + +- [API docs](./api/theindex.html) +- [nim-lang.org](https://nim-lang.org) +- [Four-oh-four](./nosuchpage.md) + diff --git a/nimdoc/bookproject/code1.nim b/nimdoc/bookproject/code1.nim new file mode 100644 index 0000000000..61432d416a --- /dev/null +++ b/nimdoc/bookproject/code1.nim @@ -0,0 +1,2 @@ +proc double*(x: int): int = + x * 2 diff --git a/nimdoc/bookproject/code2.nim b/nimdoc/bookproject/code2.nim new file mode 100644 index 0000000000..d25ea2c559 --- /dev/null +++ b/nimdoc/bookproject/code2.nim @@ -0,0 +1,10 @@ +proc single*(x: int): int = + x * 1 + +#doublestart +proc double*(x: int): int = + x * 2 +#doubleend + +proc triple*(x: int): int = + x * 3 diff --git a/nimdoc/bookproject/code3.py b/nimdoc/bookproject/code3.py new file mode 100644 index 0000000000..948f144cc4 --- /dev/null +++ b/nimdoc/bookproject/code3.py @@ -0,0 +1,3 @@ +# This is Python +def double(x: int) -> int: + return x * 2 diff --git a/nimdoc/bookproject/expected/api/code1.html b/nimdoc/bookproject/expected/api/code1.html new file mode 100644 index 0000000000..48f000dfdd --- /dev/null +++ b/nimdoc/bookproject/expected/api/code1.html @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<!-- This file is generated by Nim. --> +<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en" lang="en" data-theme="auto"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<title>nimdoc/bookproject/code1 + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/code1

+
+
+
+ + +
+ + + +
+ Search: +
+ +
+ Group by: + +
+ + + +
+
+ +
+ +

+
+

Procs

+
+
+
+
proc double(x: int): int {....raises: [], tags: [], forbids: [].}
+
+ + + +
+
+ +
+ +
+
+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/api/code1.idx b/nimdoc/bookproject/expected/api/code1.idx new file mode 100644 index 0000000000..94c2cb4731 --- /dev/null +++ b/nimdoc/bookproject/expected/api/code1.idx @@ -0,0 +1,2 @@ +nimTitle code1 code1.html module nimdoc/bookproject/code1 0 +nim double code1.html#double,int proc double(x: int): int 1 diff --git a/nimdoc/bookproject/expected/api/theindex.html b/nimdoc/bookproject/expected/api/theindex.html new file mode 100644 index 0000000000..7b9c9b8bfe --- /dev/null +++ b/nimdoc/bookproject/expected/api/theindex.html @@ -0,0 +1,46 @@ + + + + + + + +Index + + + + + + + + + + + + + + + + +
+ + + +
+

Index

+ Modules: code1.

API symbols

+
double:
+
+ +
+
+ + + diff --git a/nimdoc/bookproject/expected/intro.html b/nimdoc/bookproject/expected/intro.html new file mode 100644 index 0000000000..8c6bca11d5 --- /dev/null +++ b/nimdoc/bookproject/expected/intro.html @@ -0,0 +1,127 @@ + + + + + + + +Welcome to Nim Book + + + + + + + + + + + + + + + + +
+ + + +
+

Welcome to Nim Book

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

This is a test project for nim book.

+ +

Code

Inline code snippet:

+

proc twice*(a: int): int =
+  a * 2

+

This snippet is tested during documentation build:

+

proc twice*(a: int): int =
+  a * 2
+
+assert 10.twice == 20

+

The same but using .. code:: directive:

+
proc twice*(a: int): int =
+  a * 2
proc twice*(a: int): int =
+  a * 2
+
+assert 10.twice == 20

Code included from a source file:

+
proc double*(x: int): int =
+  x * 2
+

Selective inclusuion:

+
+proc double*(x: int): int =
+  x * 2
+

Doesn't have to be Nim code:

+
# This is Python
+def double(x: int) -> int:
+    return x * 2
+
+

Admonitions

Note: +General info
+
Warning: +

It's dangerous to go alone!

+

Take this!

+
+
Error: +Oh, snap :-(
+
Important: +Admonitions can contain lists and code blocks.
  • This
  • +
  • is
  • +
  • great!
  • +
+
echo "Indeed"
+ +

Links

This is a link to a heading on the same page: Code.

+

This is a link to a heading on another page: intro.md: Heading.

+

Same, but with different syntax: intro.md: Heading.

+

You can use standard Markdown syntax, too: I am a link

+

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/intro.idx b/nimdoc/bookproject/expected/intro.idx new file mode 100644 index 0000000000..71520bba74 --- /dev/null +++ b/nimdoc/bookproject/expected/intro.idx @@ -0,0 +1,4 @@ +markupTitle Welcome to Nim Book intro.html Welcome to Nim Book 0 +heading Code intro.html#code Code 0 +heading Admonitions intro.html#admonitions Admonitions 0 +heading Links intro.html#links Links 0 diff --git a/nimdoc/bookproject/expected/page1.html b/nimdoc/bookproject/expected/page1.html new file mode 100644 index 0000000000..230206798c --- /dev/null +++ b/nimdoc/bookproject/expected/page1.html @@ -0,0 +1,81 @@ + + + + + + + +Another page + + + + + + + + + + + + + + + + +
+ + + +
+

Another page

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Section 1

Paragraph.

+

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/page1.idx b/nimdoc/bookproject/expected/page1.idx new file mode 100644 index 0000000000..9dd7adaf42 --- /dev/null +++ b/nimdoc/bookproject/expected/page1.idx @@ -0,0 +1,2 @@ +markupTitle Another page page1.html Another page 0 +heading Section 1 page1.html#section-1 Section 1 0 diff --git a/nimdoc/bookproject/expected/sections/1/intro.html b/nimdoc/bookproject/expected/sections/1/intro.html new file mode 100644 index 0000000000..a575146133 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/intro.html @@ -0,0 +1,81 @@ + + + + + + + +Welcome to Section 1 + + + + + + + + + + + + + + + + +
+ + + +
+

Welcome to Section 1

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Heading

Paragraph.

+

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/1/intro.idx b/nimdoc/bookproject/expected/sections/1/intro.idx new file mode 100644 index 0000000000..78bff4b906 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/intro.idx @@ -0,0 +1,2 @@ +markupTitle Welcome to Section 1 sections/1/intro.html Welcome to Section 1 0 +heading Heading sections/1/intro.html#heading Heading 0 diff --git a/nimdoc/bookproject/expected/sections/1/page2.html b/nimdoc/bookproject/expected/sections/1/page2.html new file mode 100644 index 0000000000..fe8e4ab62a --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/page2.html @@ -0,0 +1,80 @@ + + + + + + + +nimdoc/bookproject/sections/1/page2 + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/sections/1/page2

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Page 2

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/1/page2.idx b/nimdoc/bookproject/expected/sections/1/page2.idx new file mode 100644 index 0000000000..83b3a9b4b8 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/page2.idx @@ -0,0 +1,2 @@ +markupTitle page2.md sections/1/page2.html page2.md 0 +heading Page 2 sections/1/page2.html#page-2 Page 2 0 diff --git a/nimdoc/bookproject/expected/sections/1/page2/subpage1.html b/nimdoc/bookproject/expected/sections/1/page2/subpage1.html new file mode 100644 index 0000000000..a00c82d0b2 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/page2/subpage1.html @@ -0,0 +1,80 @@ + + + + + + + +nimdoc/bookproject/sections/1/page2/subpage1 + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/sections/1/page2/subpage1

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Subpage 1

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/1/page2/subpage1.idx b/nimdoc/bookproject/expected/sections/1/page2/subpage1.idx new file mode 100644 index 0000000000..9859a15f22 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/1/page2/subpage1.idx @@ -0,0 +1,2 @@ +markupTitle subpage1.md sections/1/page2/subpage1.html subpage1.md 0 +heading Subpage 1 sections/1/page2/subpage1.html#subpage-1 Subpage 1 0 diff --git a/nimdoc/bookproject/expected/sections/2/intro.html b/nimdoc/bookproject/expected/sections/2/intro.html new file mode 100644 index 0000000000..77dcd17f2c --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/intro.html @@ -0,0 +1,80 @@ + + + + + + + +nimdoc/bookproject/sections/2/intro + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/sections/2/intro

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Section 2: Intro

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/2/intro.idx b/nimdoc/bookproject/expected/sections/2/intro.idx new file mode 100644 index 0000000000..8e94a29735 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/intro.idx @@ -0,0 +1,2 @@ +markupTitle intro.md sections/2/intro.html intro.md 0 +heading Section 2: Intro sections/2/intro.html#section-2colon-intro Section 2: Intro 0 diff --git a/nimdoc/bookproject/expected/sections/2/page3.html b/nimdoc/bookproject/expected/sections/2/page3.html new file mode 100644 index 0000000000..04e646b118 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/page3.html @@ -0,0 +1,80 @@ + + + + + + + +nimdoc/bookproject/sections/2/page3 + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/sections/2/page3

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Page 3

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/2/page3.idx b/nimdoc/bookproject/expected/sections/2/page3.idx new file mode 100644 index 0000000000..0783bbd60e --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/page3.idx @@ -0,0 +1,2 @@ +markupTitle page3.md sections/2/page3.html page3.md 0 +heading Page 3 sections/2/page3.html#page-3 Page 3 0 diff --git a/nimdoc/bookproject/expected/sections/2/page3/subpage2.html b/nimdoc/bookproject/expected/sections/2/page3/subpage2.html new file mode 100644 index 0000000000..0a9b715ed4 --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/page3/subpage2.html @@ -0,0 +1,80 @@ + + + + + + + +nimdoc/bookproject/sections/2/page3/subpage2 + + + + + + + + + + + + + + + + +
+ + + +
+

nimdoc/bookproject/sections/2/page3/subpage2

+
+
+
+ + +
+ + + +
+ Search: +
+ + + + +
+
+ +
+ +

+

Subpage 2

+ +
+
+ + +
+
+ + + diff --git a/nimdoc/bookproject/expected/sections/2/page3/subpage2.idx b/nimdoc/bookproject/expected/sections/2/page3/subpage2.idx new file mode 100644 index 0000000000..65c2266cce --- /dev/null +++ b/nimdoc/bookproject/expected/sections/2/page3/subpage2.idx @@ -0,0 +1,2 @@ +markupTitle subpage2.md sections/2/page3/subpage2.html subpage2.md 0 +heading Subpage 2 sections/2/page3/subpage2.html#subpage-2 Subpage 2 0 diff --git a/nimdoc/bookproject/expected/theindex.html b/nimdoc/bookproject/expected/theindex.html new file mode 100644 index 0000000000..0ecd78b58a --- /dev/null +++ b/nimdoc/bookproject/expected/theindex.html @@ -0,0 +1,82 @@ + + + + + + + +Index + + + + + + + + + + + + + + + + + + + + diff --git a/nimdoc/bookproject/intro.md b/nimdoc/bookproject/intro.md new file mode 100644 index 0000000000..76a8672b0b --- /dev/null +++ b/nimdoc/bookproject/intro.md @@ -0,0 +1,85 @@ +.. title:: Welcome to Nim Book +.. importdoc:: page1 +.. importdoc:: sections/1/intro + +This is a test project for `nim book`:cmd:. + +# Code + +Inline code snippet: + +```nim +proc twice*(a: int): int = + a * 2 +``` + +This snippet is tested during documentation build: + +```nim test +proc twice*(a: int): int = + a * 2 + +assert 10.twice == 20 +``` + +The same but using `.. code::` directive: + +.. code:: + proc twice*(a: int): int = + a * 2 + +.. code:: + :test: + + proc twice*(a: int): int = + a * 2 + + assert 10.twice == 20 + +Code included from a source file: + +.. include:: ./code1.nim + :code: + +Selective inclusuion: + +.. include:: ./code2.nim + :code: + :start-after:#doublestart + :end-before:#doubleend + +Doesn't have to be Nim code: + +.. include:: ./code3.py + :code: python + +# Admonitions + +.. note:: General info + +.. warning:: + It's dangerous to go alone! + + Take this! + +.. error:: Oh, snap :-( + +.. important:: + Admonitions can contain lists and code blocks. + + - This + - is + - great! + + .. code-block:: + echo "Indeed" + +# Links + +This is a link to a heading on the same page: [Code]. + +This is a link to a heading on another page: [Heading]. + +Same, but with different syntax: `Heading`_. + +You can use standard Markdown syntax, too: [I am a link](page1.html#heading) diff --git a/nimdoc/bookproject/page1.md b/nimdoc/bookproject/page1.md new file mode 100644 index 0000000000..31e8dfb9da --- /dev/null +++ b/nimdoc/bookproject/page1.md @@ -0,0 +1,5 @@ +.. title:: Another page + +# Section 1 + +Paragraph. diff --git a/nimdoc/bookproject/sections/1/intro.md b/nimdoc/bookproject/sections/1/intro.md new file mode 100644 index 0000000000..a336d70b79 --- /dev/null +++ b/nimdoc/bookproject/sections/1/intro.md @@ -0,0 +1,5 @@ +.. title:: Welcome to Section 1 + +# Heading + +Paragraph. diff --git a/nimdoc/bookproject/sections/1/page2.md b/nimdoc/bookproject/sections/1/page2.md new file mode 100644 index 0000000000..f310be3320 --- /dev/null +++ b/nimdoc/bookproject/sections/1/page2.md @@ -0,0 +1 @@ +# Page 2 diff --git a/nimdoc/bookproject/sections/1/page2/subpage1.md b/nimdoc/bookproject/sections/1/page2/subpage1.md new file mode 100644 index 0000000000..a5284eb324 --- /dev/null +++ b/nimdoc/bookproject/sections/1/page2/subpage1.md @@ -0,0 +1 @@ +# Subpage 1 diff --git a/nimdoc/bookproject/sections/2/intro.md b/nimdoc/bookproject/sections/2/intro.md new file mode 100644 index 0000000000..41dd310200 --- /dev/null +++ b/nimdoc/bookproject/sections/2/intro.md @@ -0,0 +1 @@ +# Section 2: Intro diff --git a/nimdoc/bookproject/sections/2/page3.md b/nimdoc/bookproject/sections/2/page3.md new file mode 100644 index 0000000000..294d95c1b6 --- /dev/null +++ b/nimdoc/bookproject/sections/2/page3.md @@ -0,0 +1 @@ +# Page 3 diff --git a/nimdoc/bookproject/sections/2/page3/subpage2.md b/nimdoc/bookproject/sections/2/page3/subpage2.md new file mode 100644 index 0000000000..73c2157f64 --- /dev/null +++ b/nimdoc/bookproject/sections/2/page3/subpage2.md @@ -0,0 +1 @@ +# Subpage 2 diff --git a/nimdoc/booktester.nim b/nimdoc/booktester.nim new file mode 100644 index 0000000000..31ba49c229 --- /dev/null +++ b/nimdoc/booktester.nim @@ -0,0 +1,53 @@ +# To run this, cd to the git repo root, and run "nim r nimdoc/booketester.nim". +# to change expected results (after carefully verifying everything), use -d:nimTestsNimdocFixup + +import strutils, os +from std/private/gitutils import diffFiles + +const fixup = defined(nimTestsNimdocFixup) + +var + failures = 0 + +const + prjDir = "nimdoc" / "bookproject" + expDir = "expected" + outDir = "book" + +proc exec(cmd: string) = + if execShellCmd(cmd) != 0: + quit("FAILURE: " & cmd) + +proc testNimBook(fixup = false) = + putEnv("SOURCE_DATE_EPOCH", "100000") + const nimExe = getCurrentCompilerExe() + + exec("$1 doc --index:on --project --outdir:$2 $3" % [nimExe, + prjDir / outDir / "api", + prjDir / "code1.nim"]) + exec("$1 book --index:only --outdir:$2 $3" % [nimExe, prjDir / outDir, prjDir]) + exec("$1 book --outdir:$2 $3" % [nimExe, prjDir / outDir, prjDir]) + + for expected in walkDirRec(prjDir / expDir, checkDir=true): + let versionCacheParam = "?v=" & $NimMajor & "." & $NimMinor & "." & $NimPatch + let produced = expected.replace('\\', '/').replace("/$1/" % [expDir], "/$1/" % [outDir]) + if not fileExists(produced): + echo "FAILURE: files not found: ", produced + inc failures + let producedFile = readFile(produced).replace(versionCacheParam,"") + if readFile(expected) != producedFile: + echo "FAILURE: files differ: ", produced + echo diffFiles(expected, produced).output + inc failures + if fixup: + writeFile(expected, producedFile) + else: + echo "SUCCESS: files identical: ", produced + + if failures == 0: + removeDir(prjDir / outDir) + +testNimBook(fixup) + +if failures > 0: + quit "$# failures occurred; see note in nimdoc/tester.nim regarding -d:nimTestsNimdocFixup" % $failures diff --git a/nimdoc/extlinks/project/expected/_._/util.html b/nimdoc/extlinks/project/expected/_._/util.html index 37be00501e..c13ccda1ae 100644 --- a/nimdoc/extlinks/project/expected/_._/util.html +++ b/nimdoc/extlinks/project/expected/_._/util.html @@ -31,28 +31,34 @@
- - -
+ + +
+ -
- Search: -
-
- Group by: - -
+ + + +
+ +
+ Search: +
+ +
+ Group by: + +
+
  • diff --git a/nimdoc/extlinks/project/expected/main.html b/nimdoc/extlinks/project/expected/main.html index 7ee68ca119..5a66a153cb 100644 --- a/nimdoc/extlinks/project/expected/main.html +++ b/nimdoc/extlinks/project/expected/main.html @@ -31,28 +31,34 @@
    - - -
    + + +
    + -
    - Search: -
    -
    - Group by: - -
    + + + +
    + +
    + Search: +
    + +
    + Group by: + +
    +
    • my heading
    • diff --git a/nimdoc/extlinks/project/expected/sub/submodule.html b/nimdoc/extlinks/project/expected/sub/submodule.html index 1b38da944f..1cdc94a43d 100644 --- a/nimdoc/extlinks/project/expected/sub/submodule.html +++ b/nimdoc/extlinks/project/expected/sub/submodule.html @@ -31,28 +31,34 @@
      - - -
      + + +
      + -
      - Search: -
      -
      - Group by: - -
      + + + +
      + +
      + Search: +
      + +
      + Group by: + +
      +
      • diff --git a/nimdoc/rst2html/expected/rst_examples.html b/nimdoc/rst2html/expected/rst_examples.html index ceadfeb5a3..d0a4935d1c 100644 --- a/nimdoc/rst2html/expected/rst_examples.html +++ b/nimdoc/rst2html/expected/rst_examples.html @@ -31,28 +31,27 @@
        - - -
        + + +
        + -
        - Search: -
        -
        - Group by: - -
        + + + +
        + +
        + Search: +
        + +
        • About this document
          • Encoding
          • diff --git a/nimdoc/test_doctype/expected/test_doctype.html b/nimdoc/test_doctype/expected/test_doctype.html index 548deb37e3..5b00a7f033 100644 --- a/nimdoc/test_doctype/expected/test_doctype.html +++ b/nimdoc/test_doctype/expected/test_doctype.html @@ -31,28 +31,34 @@
            - - -
            + + +
            + -
            - Search: -
            -
            - Group by: - -
            + + + +
            + +
            + Search: +
            + +
            + Group by: + +
            +
            • Check
            • text
            • diff --git a/nimdoc/test_out_index_dot_html/expected/index.html b/nimdoc/test_out_index_dot_html/expected/index.html index e287ec60fa..249bd70cef 100644 --- a/nimdoc/test_out_index_dot_html/expected/index.html +++ b/nimdoc/test_out_index_dot_html/expected/index.html @@ -31,28 +31,34 @@
              - - -
              + + +
              + -
              - Search: -
              -
              - Group by: - -
              + + + +
              + +
              + Search: +
              + +
              + Group by: + +
              +
              • diff --git a/nimdoc/testproject/expected/nimdoc.out.css b/nimdoc/testproject/expected/nimdoc.out.css index f4ac27b28d..6288d2d9cc 100644 --- a/nimdoc/testproject/expected/nimdoc.out.css +++ b/nimdoc/testproject/expected/nimdoc.out.css @@ -553,6 +553,11 @@ ul.nested-toc-section { ul.nested-toc-section > li { margin-left: 1.25em; } +ul.simple-toc-section a.current, +ul.nested-toc-section a.current { + font-weight: bold; + color: var(--anchor-focus); +} ol.arabic { list-style: decimal; } @@ -1139,3 +1144,7 @@ span.pragmawrap { span.attachedType { display: none; visibility: hidden; } + +summary { + cursor: pointer; +} diff --git a/nimdoc/testproject/expected/subdir/subdir_b/utils.html b/nimdoc/testproject/expected/subdir/subdir_b/utils.html index 6e56d9d93d..8e42a8d439 100644 --- a/nimdoc/testproject/expected/subdir/subdir_b/utils.html +++ b/nimdoc/testproject/expected/subdir/subdir_b/utils.html @@ -31,28 +31,34 @@
                - - -
                + + +
                + -
                - Search: -
                -
                - Group by: - -
                + + + +
                + +
                + Search: +
                + +
                + Group by: + +
                +
                • This is now a header
                  • Next header
                  • diff --git a/nimdoc/testproject/expected/testproject.html b/nimdoc/testproject/expected/testproject.html index 7bf409cfff..6e580f0142 100644 --- a/nimdoc/testproject/expected/testproject.html +++ b/nimdoc/testproject/expected/testproject.html @@ -31,28 +31,34 @@
                    - - -
                    + + +
                    + -
                    - Search: -
                    -
                    - Group by: - -
                    + + + +
                    + +
                    + Search: +
                    + +
                    + Group by: + +
                    +
                    • Basic usage
                      • Encoding data
                      • diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim index 97dbaacd72..37af7f2d30 100644 --- a/tests/stdlib/trstgen.nim +++ b/tests/stdlib/trstgen.nim @@ -1307,6 +1307,36 @@ Test1 doAssert "endOfNote" in output3 doAssert "class=\"admonition admonition-info\"" in output3 + let input4 = dedent""" + .. admonition:: + :title: Custom title + :collapsible: + + endOfAdmonition + """ + + let output4 = input4.toHtml( + NoSandboxOpts + ) + doAssert "Custom title" in output4 + doAssert "
                        " in output4 + doAssert "endOfAdmonition
                        " in output4 + + let input5 = dedent""" + .. admonition:: + :title: Custom title + :collapsible: closed + + endOfAdmonition + """ + + let output5 = input5.toHtml( + NoSandboxOpts + ) + doAssert "Custom title" in output5 + doAssert "
                        " in output5 + doAssert "endOfAdmonition
                        " in output5 + test "RST internal links": let input1 = dedent """ Start.