diff options
author | Andrey Makarov <ph.makarov@gmail.com> | 2022-08-28 00:28:26 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-27 17:28:26 -0400 |
commit | de9cbf6af1b3e67ceb78c4fd02c88b48fe762fa8 (patch) | |
tree | 5dfb0b2b90b3f6814ef2a36df650d90dacf39af9 | |
parent | 9ca63451227c879c573c280e582ca51af034be9d (diff) | |
download | Nim-de9cbf6af1b3e67ceb78c4fd02c88b48fe762fa8.tar.gz |
Fix auto links to subheader when TOC is present (#20279)
Fix links to subheader when TOC is present It was observed (in https://github.com/nim-lang/Nim/pull/20112) that links to 2nd- (and subsequent) -level headings fail if TOC is present, e.g.: ```nim .. contents:: Type relations ============== Convertible relation -------------------- Ref. `Convertible relation`_ ``` The problem here is that links are resolved in `rst.nim` but later `rstgen.nim` fixes ("fixes") anchors to make them unique so that TOC always works (if e.g. there was another sub-section like "Convertible relation"). The solution implemented in this PR is to move that fix-up of anchors into `rst.nim`, so that link resolution could know final anchors. The bug seems to be added in https://github.com/nim-lang/Nim/pull/2332 in 2015, that is it is present in Nim 1.0.
-rw-r--r-- | compiler/docgen.nim | 19 | ||||
-rw-r--r-- | compiler/docgen2.nim | 3 | ||||
-rw-r--r-- | lib/packages/docutils/rst.nim | 135 | ||||
-rw-r--r-- | lib/packages/docutils/rstgen.nim | 53 | ||||
-rw-r--r-- | tests/stdlib/trst.nim | 2 | ||||
-rw-r--r-- | tests/stdlib/trstgen.nim | 57 |
6 files changed, 165 insertions, 104 deletions
diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 875089c49..4a914b0e7 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -281,7 +281,8 @@ proc isLatexCmd(conf: ConfigRef): bool = conf.cmd in {cmdRst2tex, cmdDoc2tex} proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, outExt: string = HtmlExt, module: PSym = nil, - standaloneDoc = false, preferMarkdown = true): PDoc = + standaloneDoc = false, preferMarkdown = true, + hasToc = true): PDoc = declareClosures() new(result) result.module = module @@ -294,9 +295,10 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, options.incl roPreferMarkdown if not standaloneDoc: options.incl roNimFile # (options can be changed dynamically in `setDoctype` by `{.doctype.}`) + result.hasToc = hasToc result.sharedState = newRstSharedState( options, filename.string, - docgenFindFile, compilerMsgHandler) + docgenFindFile, compilerMsgHandler, hasToc) initRstGenerator(result[], (if conf.isLatexCmd: outLatex else: outHtml), conf.configVars, filename.string, docgenFindFile, compilerMsgHandler) @@ -1339,6 +1341,7 @@ proc finishGenerateDoc*(d: var PDoc) = if fragment.isRst: firstRst = fragment.rst break + d.hasToc = d.hasToc or d.sharedState.hasToc preparePass2(d.sharedState, firstRst) # add anchors to overload groups before RST resolution @@ -1396,7 +1399,6 @@ proc finishGenerateDoc*(d: var PDoc) = d.section[k].secItems.clear renderItemPre(d, d.modDescPre, d.modDescFinal) d.modDescPre.setLen 0 - d.hasToc = d.hasToc or d.sharedState.hasToc # Finalize fragments of ``.json`` file for i, entry in d.jEntriesPre: @@ -1685,8 +1687,7 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = handleDocOutputOptions conf var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(conf.projectFull, cache, conf) - d.hasToc = true + var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true) generateDoc(d, ast, ast) finishGenerateDoc(d) writeOutput(d) @@ -1697,7 +1698,7 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef; preferMarkdown: bool) = var filen = addFileExt(filename, "txt") var d = newDocumentor(filen, cache, conf, outExt, standaloneDoc = true, - preferMarkdown = preferMarkdown) + preferMarkdown = preferMarkdown, hasToc = false) let rst = parseRst(readFile(filen.string), line=LineRstInit, column=ColRstInit, conf, d.sharedState) @@ -1718,12 +1719,11 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = ## implementation of a deprecated jsondoc0 command var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(conf.projectFull, cache, conf) + var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true) d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), warnUser, "the ':test:' attribute is not supported by this backend") - d.hasToc = true generateJson(d, ast) finishGenerateDoc(d) let json = d.jEntriesFinal @@ -1742,12 +1742,11 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = proc commandTags*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return - var d = newDocumentor(conf.projectFull, cache, conf) + var d = newDocumentor(conf.projectFull, cache, conf, hasToc = true) d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), warnUser, "the ':test:' attribute is not supported by this backend") - d.hasToc = true var content = "" generateTags(d, ast, content) diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 9abde9f52..ea3253a2c 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -64,8 +64,7 @@ template myOpenImpl(ext: untyped) {.dirty.} = g.module = module g.config = graph.config var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position), - graph.cache, graph.config, ext, module) - d.hasToc = true + graph.cache, graph.config, ext, module, hasToc = true) g.doc = d result = g diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 9bc3c604a..5d61dc8c3 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -556,17 +556,17 @@ type footnoteAnchor = "footnote anchor", headlineAnchor = "implicitly-generated headline anchor" AnchorSubst = object - mainAnchor: ref string # A reference name that will be inserted directly - # into HTML/Latex. It's declared as `ref` because - # it can be shared between aliases. info: TLineInfo # where the anchor was defined priority: int case kind: range[arInternalRst .. arNim] of arInternalRst: anchorType: RstAnchorKind + target: PRstNode of arNim: tooltip: string # displayed tooltip for Nim-generated anchors langSym: LangSymbol + refname: string # A reference name that will be inserted directly + # into HTML/Latex. AnchorSubstTable = Table[string, seq[AnchorSubst]] # use `seq` to account for duplicate anchors FootnoteType = enum @@ -610,9 +610,14 @@ type filenames*: RstFileTable # map file name <-> FileIndex (for storing # file names for warnings after 1st stage) currFileIdx*: FileIndex # current index in `filenames` + tocPart*: seq[PRstNode] # all the headings of a document hasToc*: bool PRstSharedState* = ref RstSharedState + ManualAnchor = object + alias: string # a (short) name that can substitute the `anchor` + anchor: string # anchor = id = refname + info: TLineInfo RstParser = object of RootObj idx*: int tok*: TokenSeq @@ -622,8 +627,9 @@ type ## documenation fragment that will be added ## in case of error/warning reporting to ## (relative) line/column of the token. - curAnchor*: string # variable to track latest anchor in s.anchors - curAnchorName*: string # corresponding name in human-readable format + curAnchors*: seq[ManualAnchor] + ## seq to accumulate aliases for anchors: + ## because RST can have >1 alias per 1 anchor EParseError* = object of ValueError @@ -705,14 +711,16 @@ proc currFilename(s: PRstSharedState): string = proc newRstSharedState*(options: RstParseOptions, filename: string, findFile: FindFileHandler, - msgHandler: MsgHandler): PRstSharedState = + msgHandler: MsgHandler, + hasToc: bool): PRstSharedState = let r = defaultRole(options) result = PRstSharedState( currRole: r, currRoleKind: whichRoleAux(r), options: options, msgHandler: if not isNil(msgHandler): msgHandler else: defaultMsgHandler, - findFile: if not isNil(findFile): findFile else: defaultFindFile + findFile: if not isNil(findFile): findFile else: defaultFindFile, + hasToc: hasToc ) setCurrFilename(result, filename) @@ -961,40 +969,28 @@ proc internalRefPriority(k: RstAnchorKind): int = of footnoteAnchor: result = 4 of headlineAnchor: result = 3 -proc addAnchorRst(p: var RstParser, name: string, refn: string, reset: bool, +proc addAnchorRst(p: var RstParser, name: string, target: PRstNode, anchorType: RstAnchorKind) = - ## Adds anchor `refn` with an alias `name` and - ## updates the corresponding `curAnchor` / `curAnchorName`. + ## Associates node `target` (which has field `anchor`) with an + ## alias `name` and updates the corresponding aliases in `p.curAnchors`. let prio = internalRefPriority(anchorType) - if p.curAnchorName == "": - var anchRef = new string - anchRef[] = refn - p.s.anchors.mgetOrPut(name, newSeq[AnchorSubst]()).add( - AnchorSubst(kind: arInternalRst, mainAnchor: anchRef, priority: prio, - info: prevLineInfo(p), anchorType: anchorType)) - else: - # override previous mainAnchor by `ref` in all aliases - var anchRef = p.s.anchors[p.curAnchorName][0].mainAnchor - anchRef[] = refn + for a in p.curAnchors: + p.s.anchors.mgetOrPut(a.alias, newSeq[AnchorSubst]()).add( + AnchorSubst(kind: arInternalRst, target: target, priority: prio, + info: a.info, anchorType: manualDirectiveAnchor)) + if name != "": p.s.anchors.mgetOrPut(name, newSeq[AnchorSubst]()).add( - AnchorSubst(kind: arInternalRst, mainAnchor: anchRef, priority: prio, + AnchorSubst(kind: arInternalRst, target: target, priority: prio, info: prevLineInfo(p), anchorType: anchorType)) - if reset: - p.curAnchor = "" - p.curAnchorName = "" - else: - p.curAnchor = refn - p.curAnchorName = name + p.curAnchors.setLen 0 proc addAnchorNim*(s: var PRstSharedState, refn: string, tooltip: string, langSym: LangSymbol, priority: int, info: TLineInfo) = - ## Adds an anchor `refn` (`mainAnchor`), which follows + ## Adds an anchor `refn`, which follows ## the rule `arNim` (i.e. a symbol in ``*.nim`` file) - var anchRef = new string - anchRef[] = refn s.anchors.mgetOrPut(langSym.name, newSeq[AnchorSubst]()).add( - AnchorSubst(kind: arNim, mainAnchor: anchRef, langSym: langSym, + AnchorSubst(kind: arNim, refname: refn, langSym: langSym, tooltip: tooltip, priority: priority, info: info)) @@ -1166,10 +1162,9 @@ proc getAutoSymbol(s: PRstSharedState, order: int): string = proc newRstNodeA(p: var RstParser, kind: RstNodeKind): PRstNode = ## create node and consume the current anchor result = newRstNode(kind) - if p.curAnchor != "": - result.anchor = p.curAnchor - p.curAnchor = "" - p.curAnchorName = "" + if p.curAnchors.len > 0: + result.anchor = p.curAnchors[0].anchor + addAnchorRst(p, "", result, manualDirectiveAnchor) template newLeaf(s: string): PRstNode = newRstLeaf(s) @@ -1867,8 +1862,8 @@ proc parseInline(p: var RstParser, father: PRstNode) = var n = newRstNode(rnInlineTarget) inc p.idx parseUntil(p, n, "`", false) - let refn = rstnodeToRefname(n) - addAnchorRst(p, name = linkName(n), refn = refn, reset = true, + n.anchor = rstnodeToRefname(n) + addAnchorRst(p, name = linkName(n), target = n, anchorType=manualInlineAnchor) father.add(n) elif isMarkdownCodeBlock(p): @@ -2557,8 +2552,8 @@ proc parseHeadline(p: var RstParser): PRstNode = result.level = getLevel(p, c, hasOverline=false) checkHeadingHierarchy(p, result.level) p.s.hCurLevel = result.level - addAnchorRst(p, linkName(result), rstnodeToRefname(result), reset=true, - anchorType=headlineAnchor) + addAnchorRst(p, linkName(result), result, anchorType=headlineAnchor) + p.s.tocPart.add result proc parseOverline(p: var RstParser): PRstNode = var c = currentTok(p).symbol[0] @@ -2580,8 +2575,36 @@ proc parseOverline(p: var RstParser): PRstNode = if currentTok(p).kind == tkAdornment: inc p.idx if currentTok(p).kind == tkIndent: inc p.idx - addAnchorRst(p, linkName(result), rstnodeToRefname(result), reset=true, - anchorType=headlineAnchor) + addAnchorRst(p, linkName(result), result, anchorType=headlineAnchor) + p.s.tocPart.add result + +proc fixHeadlines(s: PRstSharedState) = + # Fix up section levels depending on presence of a title and subtitle: + for n in s.tocPart: + if n.kind in {rnHeadline, rnOverline}: + if s.hTitleCnt == 2: + if n.level == 1: # it's the subtitle + n.level = 0 + elif n.level >= 2: # normal sections, start numbering from 1 + n.level -= 1 + elif s.hTitleCnt == 0: + n.level += 1 + # Set headline anchors: + for iHeading in 0 .. s.tocPart.high: + let n: PRstNode = s.tocPart[iHeading] + if n.level >= 1: + n.anchor = rstnodeToRefname(n) + # Fix anchors for uniqueness if `.. contents::` is present + if s.hasToc: + # Find the last higher level section for unique reference name + var sectionPrefix = "" + for i in countdown(iHeading - 1, 0): + if s.tocPart[i].level >= 1 and s.tocPart[i].level < n.level: + sectionPrefix = rstnodeToRefname(s.tocPart[i]) & "-" + break + if sectionPrefix != "": + n.anchor = sectionPrefix & n.anchor + s.tocPart.setLen 0 type ColSpec = object @@ -3269,6 +3292,7 @@ proc dirTitle(p: var RstParser): PRstNode = proc dirContents(p: var RstParser): PRstNode = result = parseDirective(p, rnContents, {hasArg}, nil) + p.s.hasToc = true proc dirIndex(p: var RstParser): PRstNode = result = parseDirective(p, rnIndex, {}, parseSectionWrapper) @@ -3403,7 +3427,7 @@ proc parseFootnote(p: var RstParser): PRstNode {.gcsafe.} = anchor.add $p.s.lineFootnoteSym.len of fnCitation: anchor.add rstnodeToRefname(label) - addAnchorRst(p, anchor, anchor, reset=true, anchorType=footnoteAnchor) + addAnchorRst(p, anchor, target = result, anchorType = footnoteAnchor) result.anchor = anchor if currentTok(p).kind == tkWhite: inc p.idx discard parseBlockContent(p, result, parseSectionWrapper) @@ -3437,8 +3461,9 @@ proc parseDotDot(p: var RstParser): PRstNode = if currentTok(p).kind == tkWhite: inc p.idx var b = untilEol(p) if len(b) == 0: # set internal anchor - addAnchorRst(p, linkName(a), rstnodeToRefname(a), reset=false, - anchorType=manualDirectiveAnchor) + p.curAnchors.add ManualAnchor( + alias: linkName(a), anchor: rstnodeToRefname(a), info: prevLineInfo(p) + ) else: # external hyperlink setRef(p, rstnodeToRefname(a), b, refType=hyperlinkAlias) elif match(p, p.idx, " |"): @@ -3479,6 +3504,7 @@ proc rstParsePass1*(fragment: string, proc preparePass2*(s: PRstSharedState, mainNode: PRstNode) = ## Records titles in node `mainNode` and orders footnotes. countTitles(s, mainNode) + fixHeadlines(s) orderFootnotes(s) proc resolveLink(s: PRstSharedState, n: PRstNode) : PRstNode = @@ -3505,14 +3531,15 @@ proc resolveLink(s: PRstSharedState, n: PRstNode) : PRstNode = let substRst = findMainAnchorRst(s, text.addNodes, n.info) for subst in substRst: foundLinks.add LinkDef(ar: arInternalRst, priority: subst.priority, - target: newLeaf(subst.mainAnchor[]), + target: newLeaf(subst.target.anchor), info: subst.info, tooltip: "(" & $subst.anchorType & ")") + # find anchors automatically generated from Nim symbols if roNimFile in s.options: let substNim = findMainAnchorNim(s, signature=text, n.info) for subst in substNim: foundLinks.add LinkDef(ar: arNim, priority: subst.priority, - target: newLeaf(subst.mainAnchor[]), + target: newLeaf(subst.refname), info: subst.info, tooltip: subst.tooltip) foundLinks.sort(cmp = cmp, order = Descending) let linkText = addNodes(n) @@ -3558,15 +3585,6 @@ proc resolveSubs*(s: PRstSharedState, n: PRstNode): PRstNode = if e != "": result = newLeaf(e) else: rstMessage(s.filenames, s.msgHandler, n.info, mwUnknownSubstitution, key) - of rnHeadline, rnOverline: - # fix up section levels depending on presence of a title and subtitle - if s.hTitleCnt == 2: - if n.level == 1: # it's the subtitle - n.level = 0 - elif n.level >= 2: # normal sections - n.level -= 1 - elif s.hTitleCnt == 0: - n.level += 1 of rnRef: result = resolveLink(s, n) of rnFootnote: @@ -3617,14 +3635,12 @@ proc resolveSubs*(s: PRstSharedState, n: PRstNode): PRstNode = # TODO: correctly report ambiguities let anchorInfo = findMainAnchorRst(s, refn, n.info) if anchorInfo.len != 0: - result.add newLeaf(anchorInfo[0].mainAnchor[]) # add link + result.add newLeaf(anchorInfo[0].target.anchor) # add link else: rstMessage(s.filenames, s.msgHandler, n.info, mwBrokenLink, refn) result.add newLeaf(refn) # add link of rnLeaf: discard - of rnContents: - s.hasToc = true else: var regroup = false for i in 0 ..< n.len: @@ -3656,7 +3672,8 @@ proc rstParse*(text, filename: string, ## note that 2nd tuple element should be fed to `initRstGenerator` ## argument `filenames` (it is being filled here at least with `filename` ## and possibly with other files from RST ``.. include::`` statement). - var sharedState = newRstSharedState(options, filename, findFile, msgHandler) + var sharedState = newRstSharedState(options, filename, findFile, + msgHandler, hasToc=false) let unresolved = rstParsePass1(text, line, column, sharedState) preparePass2(sharedState, unresolved) result.node = resolveSubs(sharedState, unresolved) diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 791935da8..32d2d3483 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -58,10 +58,6 @@ type outHtml, # output is HTML outLatex # output is Latex - TocEntry = object - n*: PRstNode - refname*, header*: string - MetaEnum* = enum metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion @@ -74,7 +70,7 @@ type config*: StringTableRef splitAfter*: int # split too long entries in the TOC listingCounter*: int - tocPart*: seq[TocEntry] + tocPart*: seq[PRstNode] # headings for Table of Contents hasToc*: bool theIndex: string # Contents of the index file to be dumped at the end. findFile*: FindFileHandler @@ -120,7 +116,8 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget, config: StringTableRef, filename: string, findFile: FindFileHandler = nil, msgHandler: MsgHandler = nil, - filenames = default(RstFileTable)) = + filenames = default(RstFileTable), + hasToc = false) = ## Initializes a ``RstGenerator``. ## ## You need to call this before using a ``RstGenerator`` with any other @@ -165,6 +162,7 @@ proc initRstGenerator*(g: var RstGenerator, target: OutputTarget, g.config = config g.target = target g.tocPart = @[] + g.hasToc = hasToc g.filename = filename g.filenames = filenames g.splitAfter = 20 @@ -773,31 +771,18 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) = var tmp = "" for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp) d.currentSection = tmp - # Find the last higher level section for unique reference name - var sectionPrefix = "" - for i in countdown(d.tocPart.high, 0): - let n2 = d.tocPart[i].n - if n2.level < n.level: - sectionPrefix = rstnodeToRefname(n2) & "-" - break - var refname = sectionPrefix & rstnodeToRefname(n) var tocName = esc(d.target, renderRstToText(n), escMode = emOption) # for Latex: simple text without commands that may break TOC/hyperref if d.hasToc: - var length = len(d.tocPart) - setLen(d.tocPart, length + 1) - d.tocPart[length].refname = refname - d.tocPart[length].n = n - d.tocPart[length].header = tmp - + d.tocPart.add n dispA(d.target, result, "\n<h$1><a class=\"toc-backref\"" & "$2 href=\"#$5\">$3</a></h$1>", "\\rsth$4[$6]{$3}$2\n", - [$n.level, refname.idS, tmp, - $chr(n.level - 1 + ord('A')), refname, tocName]) + [$n.level, n.anchor.idS, tmp, + $chr(n.level - 1 + ord('A')), n.anchor, tocName]) else: dispA(d.target, result, "\n<h$1$2>$3</h$1>", "\\rsth$4[$5]{$3}$2\n", [ - $n.level, refname.idS, tmp, + $n.level, n.anchor.idS, tmp, $chr(n.level - 1 + ord('A')), tocName]) # Generate index entry using spaces to indicate TOC level for the output HTML. @@ -810,7 +795,7 @@ proc renderHeadline(d: PDoc, n: PRstNode, result: var string) = # outDir = /foo -\ # destFile = /foo/bar/zoo.html -|-> bar/zoo.html d.destFile.relativePath(d.outDir, '/') - setIndexTerm(d, htmlFileRelPath, refname, tmp.stripTocHtml, + setIndexTerm(d, htmlFileRelPath, n.anchor, tmp.stripTocHtml, spaces(max(0, n.level)) & tmp) proc renderOverline(d: PDoc, n: PRstNode, result: var string) = @@ -829,18 +814,20 @@ proc renderOverline(d: PDoc, n: PRstNode, result: var string) = var tocName = esc(d.target, renderRstToText(n), escMode=emOption) dispA(d.target, result, "<h$1$2><center>$3</center></h$1>", "\\rstov$4[$5]{$3}$2\n", [$n.level, - rstnodeToRefname(n).idS, tmp, $chr(n.level - 1 + ord('A')), tocName]) + n.anchor.idS, tmp, $chr(n.level - 1 + ord('A')), tocName]) -proc renderTocEntry(d: PDoc, e: TocEntry, result: var string) = +proc renderTocEntry(d: PDoc, n: PRstNode, result: var string) = + var header = "" + for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], header) dispA(d.target, result, "<li><a class=\"reference\" id=\"$1_toc\" href=\"#$1\">$2</a></li>\n", - "\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header]) + "\\item\\label{$1_toc} $2\\ref{$1}\n", [n.anchor, header]) proc renderTocEntries*(d: var RstGenerator, j: var int, lvl: int, result: var string) = var tmp = "" while j <= high(d.tocPart): - var a = abs(d.tocPart[j].n.level) + var a = abs(d.tocPart[j].level) if a == lvl: renderTocEntry(d, d.tocPart[j], tmp) inc(j) @@ -1630,11 +1617,12 @@ proc rstToHtml*(s: string, options: RstParseOptions, result = "" const filen = "input" - let (rst, filenames, _) = rstParse(s, filen, + let (rst, filenames, t) = rstParse(s, filen, line=LineRstInit, column=ColRstInit, options, myFindFile, msgHandler) var d: RstGenerator - initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler, filenames) + initRstGenerator(d, outHtml, config, filen, myFindFile, msgHandler, + filenames, hasToc = t) result = "" renderRstToOut(d, rst, result) strbasics.strip(result) @@ -1644,10 +1632,11 @@ proc rstToLatex*(rstSource: string; options: RstParseOptions): string {.inline, ## Convenience proc for `renderRstToOut` and `initRstGenerator`. runnableExamples: doAssert rstToLatex("*Hello* **world**", {}) == """\emph{Hello} \textbf{world}""" if rstSource.len == 0: return - let (rst, filenames, _) = rstParse(rstSource, "", + let (rst, filenames, t) = rstParse(rstSource, "", line=LineRstInit, column=ColRstInit, options) var rstGenera: RstGenerator - rstGenera.initRstGenerator(outLatex, defaultConfig(), "input", filenames=filenames) + rstGenera.initRstGenerator(outLatex, defaultConfig(), "input", + filenames=filenames, hasToc = t) rstGenera.renderRstToOut(rst, result) strbasics.strip(result) diff --git a/tests/stdlib/trst.nim b/tests/stdlib/trst.nim index d1d7a3f00..32917d257 100644 --- a/tests/stdlib/trst.nim +++ b/tests/stdlib/trst.nim @@ -74,7 +74,7 @@ suite "RST parsing": """.toAst == dedent""" rnInner - rnHeadline level=1 + rnHeadline level=1 anchor='lexical-analysis' rnLeaf 'Lexical' rnLeaf ' ' rnLeaf 'Analysis' diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim index a70b3f699..8e05f80b7 100644 --- a/tests/stdlib/trstgen.nim +++ b/tests/stdlib/trstgen.nim @@ -536,6 +536,63 @@ Some chapter let output1 = input1.toHtml doAssert output1 == "GC_step" + test "RST anchors/links to headings": + # Currently in TOC mode anchors are modified (for making links from + # the TOC unique) + let inputNoToc = dedent""" + Type relations + ============== + + Convertible relation + -------------------- + + Ref. `Convertible relation`_ + """ + let outputNoToc = inputNoToc.toHtml + check outputNoToc.count("id=\"type-relations\"") == 1 + check outputNoToc.count("id=\"convertible-relation\"") == 1 + check outputNoToc.count("href=\"#convertible-relation\"") == 1 + + let inputTocCases = @[ + dedent""" + .. contents:: + + Type relations + ============== + + Convertible relation + -------------------- + + Ref. `Convertible relation`_ + + Guards and locks + ================ + """, + dedent""" + Ref. `Convertible relation`_ + + .. contents:: + + Type relations + ============== + + Convertible relation + -------------------- + + Guards and locks + ================ + """ + ] + for inputToc in inputTocCases: + let outputToc = inputToc.toHtml + check outputToc.count("id=\"type-relations\"") == 1 + check outputToc.count("id=\"type-relations-convertible-relation\"") == 1 + check outputToc.count("id=\"convertible-relation\">") == 0 + # Besides "Ref.", heading also contains link to itself: + check outputToc.count( + "href=\"#type-relations-convertible-relation\">") == 2 + check outputToc.count("href=\"#convertible-relation\"") == 0 + test "RST links": let input1 = """ Want to learn about `my favorite programming language`_? |