mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
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))
|
||||
else:
|
||||
# override previous mainAnchor by `ref` in all aliases
|
||||
var anchRef = p.s.anchors[p.curAnchorName][0].mainAnchor
|
||||
anchRef[] = refn
|
||||
p.s.anchors.mgetOrPut(name, newSeq[AnchorSubst]()).add(
|
||||
AnchorSubst(kind: arInternalRst, mainAnchor: anchRef, 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -74,7 +74,7 @@ suite "RST parsing":
|
||||
""".toAst ==
|
||||
dedent"""
|
||||
rnInner
|
||||
rnHeadline level=1
|
||||
rnHeadline level=1 anchor='lexical-analysis'
|
||||
rnLeaf 'Lexical'
|
||||
rnLeaf ' '
|
||||
rnLeaf 'Analysis'
|
||||
|
||||
@@ -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`_?
|
||||
|
||||
Reference in New Issue
Block a user