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.