# # # Nimrod's Runtime Library # (c) Copyright 2012 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements a generator of HTML/Latex from `reStructuredText`:idx. import strutils, os, hashes, strtabs, rstast, rst, highlite const HtmlExt = "html" IndexExt* = ".idx" type TOutputTarget* = enum ## which document type to generate outHtml, # output is HTML outLatex # output is Latex TTocEntry{.final.} = object n*: PRstNode refname*, header*: string TMetaEnum* = enum metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion TRstGenerator* = object of TObject target*: TOutputTarget config*: PStringTable splitAfter*: int # split too long entries in the TOC tocPart*: seq[TTocEntry] hasToc*: bool theIndex: string options*: TRstParseOptions findFile*: TFindFileHandler msgHandler*: TMsgHandler filename*: string meta*: array[TMetaEnum, string] PDoc = var TRstGenerator proc initRstGenerator*(g: var TRstGenerator, target: TOutputTarget, config: PStringTable, filename: string, options: TRstParseOptions, findFile: TFindFileHandler, msgHandler: TMsgHandler) = g.config = config g.target = target g.tocPart = @[] g.filename = filename g.splitAfter = 20 g.theIndex = "" g.options = options g.findFile = findFile g.msgHandler = msgHandler let s = config["split.item.toc"] if s != "": g.splitAfter = parseInt(s) for i in low(g.meta)..high(g.meta): g.meta[i] = "" proc writeIndexFile*(g: var TRstGenerator, outfile: string) = if g.theIndex.len > 0: writeFile(outfile, g.theIndex) proc addXmlChar(dest: var string, c: Char) = case c of '&': add(dest, "&") of '<': add(dest, "<") of '>': add(dest, ">") of '\"': add(dest, """) else: add(dest, c) proc addRtfChar(dest: var string, c: Char) = case c of '{': add(dest, "\\{") of '}': add(dest, "\\}") of '\\': add(dest, "\\\\") else: add(dest, c) proc addTexChar(dest: var string, c: Char) = case c of '_': add(dest, "\\_") of '{': add(dest, "\\symbol{123}") of '}': add(dest, "\\symbol{125}") of '[': add(dest, "\\symbol{91}") of ']': add(dest, "\\symbol{93}") of '\\': add(dest, "\\symbol{92}") of '$': add(dest, "\\$") of '&': add(dest, "\\&") of '#': add(dest, "\\#") of '%': add(dest, "\\%") of '~': add(dest, "\\symbol{126}") of '@': add(dest, "\\symbol{64}") of '^': add(dest, "\\symbol{94}") of '`': add(dest, "\\symbol{96}") else: add(dest, c) var splitter*: string = "" proc escChar*(target: TOutputTarget, dest: var string, c: Char) {.inline.} = case target of outHtml: addXmlChar(dest, c) of outLatex: addTexChar(dest, c) proc nextSplitPoint*(s: string, start: int): int = result = start while result < len(s) + 0: case s[result] of '_': return of 'a'..'z': if result + 1 < len(s) + 0: if s[result + 1] in {'A'..'Z'}: return else: nil inc(result) dec(result) # last valid index proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string = result = "" if splitAfter >= 0: var partLen = 0 var j = 0 while j < len(s): var k = nextSplitPoint(s, j) if (splitter != " ") or (partLen + k - j + 1 > splitAfter): partLen = 0 add(result, splitter) for i in countup(j, k): escChar(target, result, s[i]) inc(partLen, k - j + 1) j = k + 1 else: for i in countup(0, len(s) - 1): escChar(target, result, s[i]) proc disp(target: TOutputTarget, xml, tex: string): string = if target != outLatex: result = xml else: result = tex proc dispF(target: TOutputTarget, xml, tex: string, args: openArray[string]): string = if target != outLatex: result = xml % args else: result = tex % args proc dispA(target: TOutputTarget, dest: var string, xml, tex: string, args: openarray[string]) = if target != outLatex: addf(dest, xml, args) else: addf(dest, tex, args) proc renderRstToOut*(d: PDoc, n: PRstNode, result: var string) proc renderAux(d: PDoc, n: PRstNode, result: var string) = for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], result) proc renderAux(d: PDoc, n: PRstNode, frmtA, frmtB: string, result: var string) = var tmp = "" for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], tmp) if d.target != outLatex: result.addf(frmtA, [tmp]) else: result.addf(frmtB, [tmp]) # ---------------- index handling -------------------------------------------- proc setIndexTerm*(d: PDoc, id, term: string) = d.theIndex.add(term) d.theIndex.add('\t') let htmlFile = changeFileExt(extractFilename(d.filename), HtmlExt) d.theIndex.add(htmlFile) d.theIndex.add('#') d.theIndex.add(id) d.theIndex.add("\n") proc hash(n: PRstNode): int = if n.kind == rnLeaf: result = hash(n.text) elif n.len > 0: result = hash(n.sons[0]) for i in 1 .. $2", "$2\\label{$1}", [id, term]) type TIndexEntry {.pure, final.} = object keyword: string link: string proc cmp(a, b: TIndexEntry): int = result = cmpIgnoreStyle(a.keyword, b.keyword) proc `<-`(a: var TIndexEntry, b: TIndexEntry) = shallowCopy a.keyword, b.keyword shallowCopy a.link, b.link proc sortIndex(a: var openArray[TIndexEntry]) = # we use shellsort here; fast and simple let N = len(a) var h = 1 while true: h = 3 * h + 1 if h > N: break while true: h = h div 3 for i in countup(h, N - 1): var v: TIndexEntry v <- a[i] var j = i while cmp(a[j-h], v) >= 0: a[j] <- a[j-h] j = j-h if j < h: break a[j] <- v if h == 1: break proc mergeIndexes*(dir: string): 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) inc L sortIndex(a) result = "" var i = 0 while i < L: result.addf("
$1
\n") i = j # ---------------------------------------------------------------------------- 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) var refname = rstnodeToRefname(n) 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 dispA(d.target, result, "$3", "\\rsth$4{$3}\\label{$2}\n", [$n.level, d.tocPart[length].refname, tmp, $chr(n.level - 1 + ord('A'))]) else: dispA(d.target, result, "$3", "\\rsth$4{$3}\\label{$2}\n", [ $n.level, refname, tmp, $chr(n.level - 1 + ord('A'))]) proc renderOverline(d: PDoc, n: PRstNode, result: var string) = if d.meta[metaTitle].len == 0: for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], d.meta[metaTitle]) elif d.meta[metaSubtitle].len == 0: for i in countup(0, len(n)-1): renderRstToOut(d, n.sons[i], d.meta[metaSubtitle]) else: var tmp = "" for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp) dispA(d.target, result, "
$3
", "\\rstov$4{$3}\\label{$2}\n", [$n.level, rstnodeToRefname(n), tmp, $chr(n.level - 1 + ord('A'))]) proc renderTocEntry(d: PDoc, e: TTocEntry, result: var string) = dispA(d.target, result, "
  • $2
  • \n", "\\item\\label{$1_toc} $2\\ref{$1}\n", [e.refname, e.header]) proc renderTocEntries*(d: PDoc, j: var int, lvl: int, result: var string) = var tmp = "" while j <= high(d.tocPart): var a = abs(d.tocPart[j].n.level) if a == lvl: renderTocEntry(d, d.tocPart[j], tmp) inc(j) elif a > lvl: renderTocEntries(d, j, a, tmp) else: break if lvl > 1: dispA(d.target, result, "", "\\begin{enumerate}$1\\end{enumerate}", [tmp]) else: result.add(tmp) proc renderImage(d: PDoc, n: PRstNode, result: var string) = var options = "" var s = getFieldValue(n, "scale") if s != "": dispA(d.target, options, " scale=\"$1\"", " scale=$1", [strip(s)]) s = getFieldValue(n, "height") if s != "": dispA(d.target, options, " height=\"$1\"", " height=$1", [strip(s)]) s = getFieldValue(n, "width") if s != "": dispA(d.target, options, " width=\"$1\"", " width=$1", [strip(s)]) s = getFieldValue(n, "alt") if s != "": dispA(d.target, options, " alt=\"$1\"", "", [strip(s)]) s = getFieldValue(n, "align") if s != "": dispA(d.target, options, " align=\"$1\"", "", [strip(s)]) if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options]) dispA(d.target, result, "", "\\includegraphics$2{$1}", [getArgument(n), options]) if len(n) >= 3: renderRstToOut(d, n.sons[2], result) proc renderSmiley(d: PDoc, n: PRstNode, result: var string) = dispA(d.target, result, """""", "\\includegraphics{$1}", [n.text]) proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) = if n.sons[2] == nil: return var m = n.sons[2].sons[0] assert m.kind == rnLeaf var langstr = strip(getArgument(n)) var lang: TSourceLanguage if langstr == "": lang = langNimrod # default language else: lang = getSourceLanguage(langstr) dispA(d.target, result, "
    ", "\\begin{rstpre}\n")
      if lang == langNone:
        d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, langstr)
        result.add(m.text)
      else:
        var g: TGeneralTokenizer
        initGeneralTokenizer(g, m.text)
        while true: 
          getNextToken(g, lang)
          case g.kind
          of gtEof: break 
          of gtNone, gtWhitespace: 
            add(result, substr(m.text, g.start, g.length + g.start - 1))
          else:
            dispA(d.target, result, "$1", "\\span$2{$1}", [
              esc(d.target, substr(m.text, g.start, g.length+g.start-1)),
              tokenClassToStr[g.kind]])
        deinitGeneralTokenizer(g)
      dispA(d.target, result, "
    ", "\n\\end{rstpre}\n") proc renderContainer(d: PDoc, n: PRstNode, result: var string) = var tmp = "" renderRstToOut(d, n.sons[2], tmp) var arg = strip(getArgument(n)) if arg == "": dispA(d.target, result, "
    $1
    ", "$1", [tmp]) else: dispA(d.target, result, "
    $2
    ", "$2", [arg, tmp]) proc texColumns(n: PRstNode): string = result = "" for i in countup(1, len(n)): add(result, "|X") proc renderField(d: PDoc, n: PRstNode, result: var string) = var b = false if d.target == outLatex: var fieldname = addNodes(n.sons[0]) var fieldval = esc(d.target, strip(addNodes(n.sons[1]))) if cmpIgnoreStyle(fieldname, "author") == 0: if d.meta[metaAuthor].len == 0: d.meta[metaAuthor] = fieldval b = true elif cmpIgnoreStyle(fieldName, "version") == 0: if d.meta[metaVersion].len == 0: d.meta[metaVersion] = fieldval b = true if not b: renderAux(d, n, "$1\n", "$1", result) proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) = if n == nil: return case n.kind of rnInner: renderAux(d, n, result) of rnHeadline: renderHeadline(d, n, result) of rnOverline: renderOverline(d, n, result) of rnTransition: renderAux(d, n, "
    \n", "\\hrule\n", result) of rnParagraph: renderAux(d, n, "

    $1

    \n", "$1\n\n", result) of rnBulletList: renderAux(d, n, "\n", "\\begin{itemize}$1\\end{itemize}\n", result) of rnBulletItem, rnEnumItem: renderAux(d, n, "
  • $1
  • \n", "\\item $1\n", result) of rnEnumList: renderAux(d, n, "
      $1
    \n", "\\begin{enumerate}$1\\end{enumerate}\n", result) of rnDefList: renderAux(d, n, "
    $1
    \n", "\\begin{description}$1\\end{description}\n", result) of rnDefItem: renderAux(d, n, result) of rnDefName: renderAux(d, n, "
    $1
    \n", "\\item[$1] ", result) of rnDefBody: renderAux(d, n, "
    $1
    \n", "$1\n", result) of rnFieldList: var tmp = "" for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp) if tmp.len != 0: dispA(d.target, result, "" & "" & "" & "$1" & "
    ", "\\begin{description}$1\\end{description}\n", [tmp]) of rnField: renderField(d, n, result) of rnFieldName: renderAux(d, n, "$1:", "\\item[$1:]", result) of rnFieldBody: renderAux(d, n, "$1", " $1\n", result) of rnIndex: renderRstToOut(d, n.sons[2], result) of rnOptionList: renderAux(d, n, "$1
    ", "\\begin{description}\n$1\\end{description}\n", result) of rnOptionListItem: renderAux(d, n, "$1\n", "$1", result) of rnOptionGroup: renderAux(d, n, "$1", "\\item[$1]", result) of rnDescription: renderAux(d, n, "$1\n", " $1\n", result) of rnOption, rnOptionString, rnOptionArgument: doAssert false, "renderRstToOut" of rnLiteralBlock: renderAux(d, n, "
    $1
    \n", "\\begin{rstpre}\n$1\n\\end{rstpre}\n", result) of rnQuotedLiteralBlock: doAssert false, "renderRstT
    /*
    :Author: David Goodger
    :Contact: goodger@python.org
    :Date: $Date: 2006-05-21 22:44:42 +0200 (Sun, 21 May 2006) $
    :Revision: $Revision: 4564 $
    :Copyright: This stylesheet has been placed in the public domain.
    
    Default cascading style sheet for the HTML output of Docutils.
    
    See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
    customize this style sheet.
    */
    
    /*
       Modified for the Nimrod Documenation by
       Andreas Rumpf
    */
    
    body {
      color: black;
      background: white;
    }
    
    /* used to remove borders from tables and images */
    .borderless, table.borderless td, table.borderless th {
      border: 0 }
    
    table.borderless td, table.