Splits index into documents and APIs.
This commit is contained in:
parent
d33edd21f3
commit
400dd20858
2 changed files with 195 additions and 47 deletions
|
|
@ -570,10 +570,10 @@ proc genOutFile(d: PDoc): PRope =
|
||||||
|
|
||||||
# Generate entries also for other entries in the toc.
|
# Generate entries also for other entries in the toc.
|
||||||
for tocPart in d.tocPart:
|
for tocPart in d.tocPart:
|
||||||
# Use to indicate the current level for the output HTML.
|
# Use spaces to indicate the current level for the output HTML.
|
||||||
assert tocPart.n.level >= 0
|
assert tocPart.n.level >= 0
|
||||||
setIndexTerm(d[], tocPart.refname, tocPart.header.stripTOCHTML,
|
setIndexTerm(d[], tocPart.refname, tocPart.header.stripTOCHTML,
|
||||||
repeatStr(max(0, tocPart.n.level), " ") & tocPart.header)
|
repeatChar(max(0, tocPart.n.level), ' ') & tocPart.header)
|
||||||
|
|
||||||
let bodyname = if d.hasToc: "doc.body_toc" else: "doc.body_no_toc"
|
let bodyname = if d.hasToc: "doc.body_toc" else: "doc.body_no_toc"
|
||||||
content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
|
content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@
|
||||||
## many options and tweaking, but you are not limited to snippets and can
|
## many options and tweaking, but you are not limited to snippets and can
|
||||||
## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.
|
## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.
|
||||||
|
|
||||||
import strutils, os, hashes, strtabs, rstast, rst, highlite
|
import strutils, os, hashes, strtabs, rstast, rst, highlite, tables, sequtils,
|
||||||
|
algorithm
|
||||||
|
|
||||||
const
|
const
|
||||||
HtmlExt = "html"
|
HtmlExt = "html"
|
||||||
|
|
@ -120,8 +121,8 @@ proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget,
|
||||||
proc writeIndexFile*(g: var TRstGenerator, outfile: string) =
|
proc writeIndexFile*(g: var TRstGenerator, outfile: string) =
|
||||||
## Writes the current index buffer to the specified output file.
|
## Writes the current index buffer to the specified output file.
|
||||||
##
|
##
|
||||||
## You previously need to add entries to the index with the ``setIndexTerm``
|
## You previously need to add entries to the index with the `setIndexTerm()
|
||||||
## proc. If the index is empty the file won't be created.
|
## <#setIndexTerm>`_ proc. If the index is empty the file won't be created.
|
||||||
if g.theIndex.len > 0: writeFile(outfile, g.theIndex)
|
if g.theIndex.len > 0: writeFile(outfile, g.theIndex)
|
||||||
|
|
||||||
proc addXmlChar(dest: var string, c: char) =
|
proc addXmlChar(dest: var string, c: char) =
|
||||||
|
|
@ -255,16 +256,21 @@ proc setIndexTerm*(d: var TRstGenerator, id, term: string,
|
||||||
## Adds a `term` to the index using the specified hyperlink identifier.
|
## Adds a `term` to the index using the specified hyperlink identifier.
|
||||||
##
|
##
|
||||||
## The ``d.theIndex`` string will be used to append the term in the format
|
## The ``d.theIndex`` string will be used to append the term in the format
|
||||||
## ``term<tab>file#id``. The anchor will be the based on the name of the file
|
## ``term<tab>file#id``.
|
||||||
## currently being parsed plus the specified `id`. The `id` will be appended
|
|
||||||
## with a hash character only if its length is not zero, otherwise no
|
|
||||||
## specific anchor will be generated. If `linkTitle` or `linkDesc` are not
|
|
||||||
## the empty string, two additional columns with their contents will be
|
|
||||||
## added.
|
|
||||||
##
|
##
|
||||||
## The index won't be written to disk unless you call ``writeIndexFile``. The
|
## The anchor will be the based on the name of the file currently being
|
||||||
## purpose of the index is documented in the `docgen tools guide
|
## parsed plus the specified `id`. The `id` will be appended with a hash
|
||||||
## <docgen.html#index-switch>`_.
|
## 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>`_ proc).
|
||||||
|
##
|
||||||
|
## If `linkTitle` or `linkDesc` are not the empty string, two additional
|
||||||
|
## columns with their contents will be added.
|
||||||
|
##
|
||||||
|
## The index won't be written to disk unless you call `writeIndexFile()
|
||||||
|
## <#writeIndexFile>`_. The purpose of the index is documented in the `docgen
|
||||||
|
## tools guide <docgen.html#index-switch>`_.
|
||||||
d.theIndex.add(term)
|
d.theIndex.add(term)
|
||||||
d.theIndex.add('\t')
|
d.theIndex.add('\t')
|
||||||
let htmlFile = changeFileExt(extractFilename(d.filename), HtmlExt)
|
let htmlFile = changeFileExt(extractFilename(d.filename), HtmlExt)
|
||||||
|
|
@ -335,43 +341,18 @@ proc sortIndex(a: var openArray[TIndexEntry]) =
|
||||||
a[j] <- v
|
a[j] <- v
|
||||||
if h == 1: break
|
if h == 1: break
|
||||||
|
|
||||||
proc mergeIndexes*(dir: string): string =
|
proc generateSymbolIndex(symbols: seq[TIndexEntry]): string =
|
||||||
## merges all index files in `dir` and returns the generated index as HTML.
|
|
||||||
## The result is no full HTML for flexibility.
|
|
||||||
var a: seq[TIndexEntry]
|
|
||||||
newSeq(a, 15_000)
|
|
||||||
setLen(a, 0)
|
|
||||||
var L = 0
|
|
||||||
for kind, path in walkDir(dir):
|
|
||||||
if kind == pcFile and path.endsWith(IndexExt):
|
|
||||||
for line in lines(path):
|
|
||||||
let s = line.find('\t')
|
|
||||||
if s < 0: continue
|
|
||||||
setLen(a, L+1)
|
|
||||||
a[L].keyword = line.substr(0, s-1)
|
|
||||||
a[L].link = line.substr(s+1)
|
|
||||||
if a[L].link.find('\t') > 0:
|
|
||||||
let extraCols = a[L].link.split('\t')
|
|
||||||
a[L].link = extraCols[0]
|
|
||||||
assert extraCols.len == 3
|
|
||||||
a[L].linkTitle = extraCols[1].unquoteIndexColumn
|
|
||||||
a[L].linkDesc = extraCols[2].unquoteIndexColumn
|
|
||||||
else:
|
|
||||||
a[L].linkTitle = nil
|
|
||||||
a[L].linkDesc = nil
|
|
||||||
inc L
|
|
||||||
sortIndex(a)
|
|
||||||
result = ""
|
result = ""
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < L:
|
while i < symbols.len:
|
||||||
result.addf("<dt><span>$1</span></dt><ul class=\"simple\"><dd>\n",
|
result.addf("<dt><span>$1</span></dt><ul class=\"simple\"><dd>\n",
|
||||||
[a[i].keyword])
|
[symbols[i].keyword])
|
||||||
var j = i
|
var j = i
|
||||||
while j < L and a[i].keyword == a[j].keyword:
|
while j < symbols.len and symbols[i].keyword == symbols[j].keyword:
|
||||||
let
|
let
|
||||||
url = a[j].link
|
url = symbols[j].link
|
||||||
text = if not a[j].linkTitle.isNil: a[j].linkTitle else: url
|
text = if not symbols[j].linkTitle.isNil: symbols[j].linkTitle else: url
|
||||||
desc = if not a[j].linkDesc.isNil: a[j].linkDesc else: ""
|
desc = if not symbols[j].linkDesc.isNil: symbols[j].linkDesc else: ""
|
||||||
if desc.len > 0:
|
if desc.len > 0:
|
||||||
result.addf("""<li><a class="reference external"
|
result.addf("""<li><a class="reference external"
|
||||||
title="$3" href="$1">$2</a></li>
|
title="$3" href="$1">$2</a></li>
|
||||||
|
|
@ -382,6 +363,173 @@ proc mergeIndexes*(dir: string): string =
|
||||||
inc j
|
inc j
|
||||||
result.add("</ul></dd>\n")
|
result.add("</ul></dd>\n")
|
||||||
i = j
|
i = j
|
||||||
|
|
||||||
|
proc isDocumentationTitle(hyperlink: string): bool =
|
||||||
|
## Returns true if the hyperlink is actually a documentation title.
|
||||||
|
##
|
||||||
|
## Documentation titles lack the hash. See `mergeIndexes() <#mergeIndexes>`_
|
||||||
|
## for a more detailed explanation.
|
||||||
|
result = hyperlink.find('#') < 0
|
||||||
|
|
||||||
|
proc `or`(x, y: string): string {.inline.} =
|
||||||
|
result = if x.isNil: y else: x
|
||||||
|
|
||||||
|
proc stripTOCLevel(s: string): tuple[level: int, text: string] =
|
||||||
|
## Returns the *level* of the toc along with the text without it.
|
||||||
|
for c in 0 .. <s.len:
|
||||||
|
result.level = c
|
||||||
|
if s[c] != ' ': break
|
||||||
|
result.text = s[result.level .. <s.len]
|
||||||
|
|
||||||
|
proc indentToLevel(level: var int, newLevel: int): string =
|
||||||
|
## Returns the sequence of <ul>|</ul> characters to switch to `newLevel`.
|
||||||
|
##
|
||||||
|
## The amount of lists added/removed will be based on the `level` variable,
|
||||||
|
## which will be reset to `newLevel` at the end of the proc.
|
||||||
|
result = ""
|
||||||
|
if level == newLevel:
|
||||||
|
return
|
||||||
|
if newLevel > level:
|
||||||
|
result = repeatStr(newLevel - level, "<ul>")
|
||||||
|
else:
|
||||||
|
result = repeatStr(level - newLevel, "</ul>")
|
||||||
|
level = newLevel
|
||||||
|
|
||||||
|
proc generateDocumentationTOC(entries: seq[TIndexEntry]):
|
||||||
|
tuple[toc, titleRef: string] =
|
||||||
|
## Returns the sequence of index entries in an HTML hierarchical list.
|
||||||
|
result.toc = ""
|
||||||
|
# Build a list of levels and extracted titles to make processing easier.
|
||||||
|
var
|
||||||
|
levels: seq[tuple[level: int, text: string]]
|
||||||
|
L = 0
|
||||||
|
level = 1
|
||||||
|
levels.newSeq(entries.len)
|
||||||
|
for entry in entries:
|
||||||
|
let (rawLevel, rawText) = stripTOCLevel(entry.linkTitle or entry.keyword)
|
||||||
|
if rawLevel < 1:
|
||||||
|
# This is a normal symbol, push it *inside* one level from the last one.
|
||||||
|
levels[L].level = level + 1
|
||||||
|
else:
|
||||||
|
# The level did change, update the level indicator.
|
||||||
|
level = rawLevel
|
||||||
|
levels[L].level = rawLevel
|
||||||
|
|
||||||
|
levels[L].text = rawText
|
||||||
|
inc L
|
||||||
|
|
||||||
|
# Now generate hierarchical lists based on the precalculated levels.
|
||||||
|
result.toc = "<ul>\n"
|
||||||
|
level = 1
|
||||||
|
L = 0
|
||||||
|
while L < entries.len:
|
||||||
|
let link = entries[L].link
|
||||||
|
if link.isDocumentationTitle:
|
||||||
|
result.titleRef = link
|
||||||
|
else:
|
||||||
|
result.toc.add(level.indentToLevel(levels[L].level))
|
||||||
|
result.toc.add("<li><a href=\"" & link & "\">" &
|
||||||
|
levels[L].text & "</a>\n")
|
||||||
|
inc L
|
||||||
|
result.toc.add(level.indentToLevel(1) & "</ul>\n")
|
||||||
|
assert(not result.titleRef.isNil, "Can't use this proc on an API index")
|
||||||
|
|
||||||
|
proc generateDocumentationIndex(
|
||||||
|
docs: TTable[string, seq[TIndexEntry]]): string =
|
||||||
|
## Returns all the documentation TOCs in an HTML hierarchical list.
|
||||||
|
result = ""
|
||||||
|
|
||||||
|
# Sort the titles to generate their toc in alphabetical order.
|
||||||
|
var titles = toSeq(keys[string, seq[TIndexEntry]](docs))
|
||||||
|
sort(titles, system.cmp)
|
||||||
|
|
||||||
|
for title in titles:
|
||||||
|
let (list, titleRef) = generateDocumentationTOC(docs[title])
|
||||||
|
result.add("<ul><li><a href=\"" & titleRef & "\">" &
|
||||||
|
title & "</a>\n" & list & "</ul>\n")
|
||||||
|
|
||||||
|
proc mergeIndexes*(dir: string): 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``
|
||||||
|
## extension previously created by commands like ``nimrod doc|rst2html``
|
||||||
|
## which use the ``--index:on`` switch. These index files are the result of
|
||||||
|
## calls to `setIndexTerm() <#setIndexTerm>`_ and `writeIndexFile()
|
||||||
|
## <#writeIndexFile>`_, so they are simple tab separated files.
|
||||||
|
##
|
||||||
|
## As convention this proc will split index files into two categories:
|
||||||
|
## documentation and API. API indices will be all joined together into a
|
||||||
|
## single big sorted index, making the bulk of the final index. This is good
|
||||||
|
## for API documentation because many symbols are repated in different
|
||||||
|
## modules. On the other hand, documentation indices are essentially table of
|
||||||
|
## contents plus a few special markers. These documents will be rendered in a
|
||||||
|
## separate section which tries to maintain the order and hierarchy of the
|
||||||
|
## symbols in the index file.
|
||||||
|
##
|
||||||
|
## To differentiate between a documentation and API file a convention is
|
||||||
|
## used: indices which contain one entry without the HTML hash character (#)
|
||||||
|
## will be considered `documentation`, since this hash-less entry is the
|
||||||
|
## explicit title of the document. Indices without this explicit entry will
|
||||||
|
## be considered `generated API` extracted out of a source ``.nim`` file.
|
||||||
|
##
|
||||||
|
## Returns the merged and sorted indices into a single HTML block which can
|
||||||
|
## be further embedded into nimdoc templates.
|
||||||
|
var
|
||||||
|
symbols: seq[TIndexEntry]
|
||||||
|
docs = initTable[string, seq[TIndexEntry]](32)
|
||||||
|
newSeq(symbols, 15_000)
|
||||||
|
setLen(symbols, 0)
|
||||||
|
var L = 0
|
||||||
|
# Scan index files and build the list of symbols.
|
||||||
|
for kind, path in walkDir(dir):
|
||||||
|
if kind == pcFile and path.endsWith(IndexExt):
|
||||||
|
var
|
||||||
|
fileEntries: seq[TIndexEntry]
|
||||||
|
foundTitle = ""
|
||||||
|
F = 0
|
||||||
|
newSeq(fileEntries, 500)
|
||||||
|
setLen(fileEntries, 0)
|
||||||
|
for line in lines(path):
|
||||||
|
let s = line.find('\t')
|
||||||
|
if s < 0: continue
|
||||||
|
setLen(fileEntries, F+1)
|
||||||
|
fileEntries[F].keyword = line.substr(0, s-1)
|
||||||
|
fileEntries[F].link = line.substr(s+1)
|
||||||
|
# See if we detect a title, a link without a #foobar part.
|
||||||
|
if foundTitle.len < 1 and fileEntries[F].link.isDocumentationTitle:
|
||||||
|
foundTitle = fileEntries[F].keyword
|
||||||
|
|
||||||
|
if fileEntries[F].link.find('\t') > 0:
|
||||||
|
let extraCols = fileEntries[F].link.split('\t')
|
||||||
|
fileEntries[F].link = extraCols[0]
|
||||||
|
assert extraCols.len == 3
|
||||||
|
fileEntries[F].linkTitle = extraCols[1].unquoteIndexColumn
|
||||||
|
fileEntries[F].linkDesc = extraCols[2].unquoteIndexColumn
|
||||||
|
else:
|
||||||
|
fileEntries[F].linkTitle = nil
|
||||||
|
fileEntries[F].linkDesc = nil
|
||||||
|
inc F
|
||||||
|
# Depending on type add this to the list of symbols or table of APIs.
|
||||||
|
if foundTitle.len > 0:
|
||||||
|
docs[foundTitle] = fileEntries
|
||||||
|
else:
|
||||||
|
setLen(symbols, L + F)
|
||||||
|
for i in 0 .. <F:
|
||||||
|
symbols[L] = fileEntries[i]
|
||||||
|
inc L
|
||||||
|
|
||||||
|
result = ""
|
||||||
|
# Generate the HTML block with API documents.
|
||||||
|
if docs.len > 0:
|
||||||
|
result.add("<h2>Documentation files</h2>\n")
|
||||||
|
result.add(generateDocumentationIndex(docs))
|
||||||
|
|
||||||
|
# Generate the HTML block with symbols.
|
||||||
|
if L > 0:
|
||||||
|
sortIndex(symbols)
|
||||||
|
result.add("<h2>API symbols</h2>\n")
|
||||||
|
result.add(generateSymbolIndex(symbols))
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue