summary refs log tree commit diff stats
path: root/packages
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-05-09 01:50:08 +0200
committerAraq <rumpf_a@web.de>2012-05-09 01:50:08 +0200
commit76235348f8ccb1363100bdc686f0fafae5dacc5f (patch)
treebea06a5eafa73a9202e06955d618d88c097b3f79 /packages
parentc323ec0155cc426e604e0c53a6baf00b17e439d7 (diff)
downloadNim-76235348f8ccb1363100bdc686f0fafae5dacc5f.tar.gz
extracted documentation generator
Diffstat (limited to 'packages')
-rwxr-xr-xpackages/docutils/rst.nim17
-rw-r--r--packages/docutils/rstgen.nim608
2 files changed, 611 insertions, 14 deletions
diff --git a/packages/docutils/rst.nim b/packages/docutils/rst.nim
index cd385ccac..2a497af9a 100755
--- a/packages/docutils/rst.nim
+++ b/packages/docutils/rst.nim
@@ -38,7 +38,8 @@ type
     meGeneralParseError,
     meInvalidDirective,
     mwRedefinitionOfLabel,
-    mwUnknownSubstitution
+    mwUnknownSubstitution,
+    mwUnsupportedLanguage
   
   TMsgHandler* = proc (filename: string, line, col: int, msgKind: TMsgKind,
                        arg: string) ## what to do in case of an error
@@ -53,7 +54,8 @@ const
     meGeneralParseError: "general parse error",
     meInvalidDirective: "invalid directive: '$1'",
     mwRedefinitionOfLabel: "redefinition of label '$1'", 
-    mwUnknownSubstitution: "unknown substitution '$1'"
+    mwUnknownSubstitution: "unknown substitution '$1'",
+    mwUnsupportedLanguage: "language '$1' not supported"
   ]
 
 proc rstnodeToRefname*(n: PRstNode): string
@@ -281,17 +283,6 @@ type
 
   EParseError* = object of EInvalidValue
 
-when false:
-  proc tokInfo(p: TRstParser, tok: TToken): TLineInfo = 
-    result = newLineInfo(p.filename, p.line + tok.line, p.col + tok.col)
-
-  proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = 
-    GlobalError(tokInfo(p, p.tok[p.idx]), msgKind, arg)
-
-  proc rstMessage(p: TRstParser, msgKind: TMsgKind) = 
-    GlobalError(tokInfo(p, p.tok[p.idx]), msgKind, p.tok[p.idx].symbol)
-
-
 proc whichMsgClass*(k: TMsgKind): TMsgClass =
   ## returns which message class `k` belongs to.
   case ($k)[1]
diff --git a/packages/docutils/rstgen.nim b/packages/docutils/rstgen.nim
index 93544124f..c2a267005 100644
--- a/packages/docutils/rstgen.nim
+++ b/packages/docutils/rstgen.nim
@@ -9,14 +9,61 @@
 
 ## This module implements a generator of HTML/Latex from `reStructuredText`:idx.
 
-import strutils, strtabs, rstast
+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, "&amp;")
@@ -84,4 +131,563 @@ proc esc*(target: TOutputTarget, s: string, splitAfter = -1): string =
       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 .. <len(n):
+      result = result !& hash(n.sons[i])
+    result = !$result
+
+proc renderIndexTerm(d: PDoc, n: PRstNode, result: var string) =
+  let id = rstnodeToRefname(n) & '_' & $abs(hash(n))
+  var term = ""
+  renderAux(d, n, term)
+  setIndexTerm(d, id, term)
+  dispA(d.target, result, "<span id=\"$1\">$2</span>", "$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("<dt><span>$1</span></dt><ul class=\"simple\"><dd>\n", 
+                a[i].keyword)
+    var j = i
+    while j < L and a[i].keyword == a[j].keyword:
+      result.addf(
+        "<li><a class=\"reference external\" href=\"$1\">$1</a></li>\n", 
+        a[j].link)
+      inc j
+    result.add("</ul></dd>\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,
+        "<h$1><a class=\"toc-backref\" id=\"$2\" href=\"#$2_toc\">$3</a></h$1>", 
+        "\\rsth$4{$3}\\label{$2}\n", [$n.level, 
+        d.tocPart[length].refname, tmp, 
+        $chr(n.level - 1 + ord('A'))])
+  else:
+    dispA(d.target, result, "<h$1 id=\"$2\">$3</h$1>", 
+                            "\\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, "<h$1 id=\"$2\"><center>$3</center></h$1>", 
+                   "\\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,
+    "<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])
+
+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, "<ul class=\"simple\">$1</ul>", 
+                            "\\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, "<img src=\"$1\"$2 />", "\\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,
+    """<img src="images/smilies/$1.gif" width="15" 
+        height="17" hspace="2" vspace="2" />""",
+    "\\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, "<pre>", "\\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, "<span class=\"$2\">$1</span>", "\\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, "</pre>", "\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, "<div>$1</div>", "$1", [tmp])
+  else:
+    dispA(d.target, result, "<div class=\"$1\">$2</div>", "$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, "<tr>$1</tr>\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, "<hr />\n", "\\hrule\n", result)
+  of rnParagraph: renderAux(d, n, "<p>$1</p>\n", "$1\n\n", result)
+  of rnBulletList:
+    renderAux(d, n, "<ul class=\"simple\">$1</ul>\n",
+                    "\\begin{itemize}$1\\end{itemize}\n", result)
+  of rnBulletItem, rnEnumItem:
+    renderAux(d, n, "<li>$1</li>\n", "\\item $1\n", result)
+  of rnEnumList:
+    renderAux(d, n, "<ol class=\"simple\">$1</ol>\n",
+                    "\\begin{enumerate}$1\\end{enumerate}\n", result)
+  of rnDefList:
+    renderAux(d, n, "<dl class=\"docutils\">$1</dl>\n",
+                       "\\begin{description}$1\\end{description}\n", result)
+  of rnDefItem: renderAux(d, n, result)
+  of rnDefName: renderAux(d, n, "<dt>$1</dt>\n", "\\item[$1] ", result)
+  of rnDefBody: renderAux(d, n, "<dd>$1</dd>\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,
+          "<table class=\"docinfo\" frame=\"void\" rules=\"none\">" &
+          "<col class=\"docinfo-name\" />" &
+          "<col class=\"docinfo-content\" />" & 
+          "<tbody valign=\"top\">$1" &
+          "</tbody></table>", 
+          "\\begin{description}$1\\end{description}\n", 
+          [tmp])
+  of rnField: renderField(d, n, result)
+  of rnFieldName: 
+    renderAux(d, n, "<th class=\"docinfo-name\">$1:</th>", "\\item[$1:]", result)
+  of rnFieldBody: 
+    renderAux(d, n, "<td>$1</td>", " $1\n", result)
+  of rnIndex: 
+    renderRstToOut(d, n.sons[2], result)
+  of rnOptionList: 
+    renderAux(d, n, "<table frame=\"void\">$1</table>", 
+      "\\begin{description}\n$1\\end{description}\n", result)
+  of rnOptionListItem: 
+    renderAux(d, n, "<tr>$1</tr>\n", "$1", result)
+  of rnOptionGroup: 
+    renderAux(d, n, "<th align=\"left\">$1</th>", "\\item[$1]", result)
+  of rnDescription: 
+    renderAux(d, n, "<td align=\"left\">$1</td>\n", " $1\n", result)
+  of rnOption, rnOptionString, rnOptionArgument: 
+    doAssert false, "renderRstToOut"
+  of rnLiteralBlock:
+    renderAux(d, n, "<pre>$1</pre>\n", 
+                    "\\begin{rstpre}\n$1\n\\end{rstpre}\n", result)
+  of rnQuotedLiteralBlock: 
+    doAssert false, "renderRstToOut"
+  of rnLineBlock: 
+    renderAux(d, n, "<p>$1</p>", "$1\n\n", result)
+  of rnLineBlockItem: 
+    renderAux(d, n, "$1<br />", "$1\\\\\n", result)
+  of rnBlockQuote: 
+    renderAux(d, n, "<blockquote><p>$1</p></blockquote>\n", 
+                    "\\begin{quote}$1\\end{quote}\n", result)
+  of rnTable, rnGridTable: 
+    renderAux(d, n, 
+      "<table border=\"1\" class=\"docutils\">$1</table>", 
+      "\\begin{table}\\begin{rsttab}{" &
+        texColumns(n) & "|}\n\\hline\n$1\\end{rsttab}\\end{table}", result)
+  of rnTableRow: 
+    if len(n) >= 1:
+      if d.target == outLatex:
+        var tmp = ""
+        renderRstToOut(d, n.sons[0], tmp)
+        for i in countup(1, len(n) - 1):
+          result.add(" & ")
+          renderRstToOut(d, n.sons[i], result)
+        result.add("\\\\\n\\hline\n")
+      else:
+        result.add("<tr>")
+        renderAux(d, n, result)
+        result.add("</tr>\n")
+  of rnTableDataCell: 
+    renderAux(d, n, "<td>$1</td>", "$1", result)
+  of rnTableHeaderCell: 
+    renderAux(d, n, "<th>$1</th>", "\\textbf{$1}", result)
+  of rnLabel: 
+    doAssert false, "renderRstToOut" # used for footnotes and other
+  of rnFootnote: 
+    doAssert false, "renderRstToOut" # a footnote
+  of rnCitation: 
+    doAssert false, "renderRstToOut" # similar to footnote
+  of rnRef: 
+    var tmp = ""
+    renderAux(d, n, tmp)
+    dispA(d.target, result, "<a class=\"reference external\" href=\"#$2\">$1</a>", 
+                            "$1\\ref{$2}", [tmp, rstnodeToRefname(n)])
+  of rnStandaloneHyperlink: 
+    renderAux(d, n, 
+      "<a class=\"reference external\" href=\"$1\">$1</a>", 
+      "\\href{$1}{$1}", result)
+  of rnHyperlink:
+    var tmp0 = ""
+    var tmp1 = ""
+    renderRstToOut(d, n.sons[0], tmp0)
+    renderRstToOut(d, n.sons[1], tmp1)
+    dispA(d.target, result, "<a class=\"reference external\" href=\"$2\">$1</a>", 
+                   "\\href{$2}{$1}", 
+                   [tmp0, tmp1])
+  of rnDirArg, rnRaw: renderAux(d, n, result)
+  of rnRawHtml:
+    if d.target != outLatex:
+      result.add addNodes(lastSon(n))
+  of rnRawLatex:
+    if d.target == outLatex:
+      result.add addNodes(lastSon(n))
+      
+  of rnImage, rnFigure: renderImage(d, n, result)
+  of rnCodeBlock: renderCodeBlock(d, n, result)
+  of rnContainer: renderContainer(d, n, result)
+  of rnSubstitutionReferences, rnSubstitutionDef: 
+    renderAux(d, n, "|$1|", "|$1|", result)
+  of rnDirective:
+    renderAux(d, n, "", "", result)
+  of rnGeneralRole:
+    var tmp0 = ""
+    var tmp1 = ""
+    renderRstToOut(d, n.sons[0], tmp0)
+    renderRstToOut(d, n.sons[1], tmp1)
+    dispA(d.target, result, "<span class=\"$2\">$1</span>", "\\span$2{$1}",
+          [tmp0, tmp1])
+  of rnSub: renderAux(d, n, "<sub>$1</sub>", "\\rstsub{$1}", result)
+  of rnSup: renderAux(d, n, "<sup>$1</sup>", "\\rstsup{$1}", result)
+  of rnEmphasis: renderAux(d, n, "<em>$1</em>", "\\emph{$1}", result)
+  of rnStrongEmphasis:
+    renderAux(d, n, "<strong>$1</strong>", "\\textbf{$1}", result)
+  of rnTripleEmphasis:
+    renderAux(d, n, "<strong><em>$1</em></strong>", 
+                    "\\textbf{emph{$1}}", result)
+  of rnInterpretedText:
+    renderAux(d, n, "<cite>$1</cite>", "\\emph{$1}", result)
+  of rnIdx:
+    renderIndexTerm(d, n, result)
+  of rnInlineLiteral: 
+    renderAux(d, n, 
+      "<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>", 
+      "\\texttt{$1}", result)
+  of rnSmiley: renderSmiley(d, n, result)
+  of rnLeaf: result.add(esc(d.target, n.text))
+  of rnContents: d.hasToc = true
+  of rnTitle:
+    d.meta[metaTitle] = ""
+    renderRstToOut(d, n.sons[0], d.meta[metaTitle])
+
+# -----------------------------------------------------------------------------
+
+proc getVarIdx(varnames: openarray[string], id: string): int = 
+  for i in countup(0, high(varnames)): 
+    if cmpIgnoreStyle(varnames[i], id) == 0: 
+      return i
+  result = -1
+
+proc formatNamedVars*(frmt: string, varnames: openarray[string], 
+                      varvalues: openarray[string]): string = 
+  var i = 0
+  var L = len(frmt)
+  result = ""
+  var num = 0
+  while i < L: 
+    if frmt[i] == '$': 
+      inc(i)                  # skip '$'
+      case frmt[i]
+      of '#': 
+        add(result, varvalues[num])
+        inc(num)
+        inc(i)
+      of '$': 
+        add(result, "$")
+        inc(i)
+      of '0'..'9': 
+        var j = 0
+        while true: 
+          j = (j * 10) + Ord(frmt[i]) - ord('0')
+          inc(i)
+          if i > L-1 or frmt[i] notin {'0'..'9'}: break 
+        if j > high(varvalues) + 1:
+          raise newException(EInvalidValue, "invalid index: " & $j)
+        num = j
+        add(result, varvalues[j - 1])
+      of 'A'..'Z', 'a'..'z', '\x80'..'\xFF': 
+        var id = ""
+        while true: 
+          add(id, frmt[i])
+          inc(i)
+          if frmt[i] notin {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}: break 
+        var idx = getVarIdx(varnames, id)
+        if idx >= 0: 
+          add(result, varvalues[idx])
+        else:
+          raise newException(EInvalidValue, "unknown substitution var: " & id)
+      of '{': 
+        var id = ""
+        inc(i)
+        while frmt[i] != '}': 
+          if frmt[i] == '\0': 
+            raise newException(EInvalidValue, "'}' expected")
+          add(id, frmt[i])
+          inc(i)
+        inc(i)                # skip }
+                              # search for the variable:
+        var idx = getVarIdx(varnames, id)
+        if idx >= 0: add(result, varvalues[idx])
+        else: 
+          raise newException(EInvalidValue, "unknown substitution var: " & id)
+      else:
+        raise newException(EInvalidValue, "unknown substitution: $" & $frmt[i])
+    var start = i
+    while i < L: 
+      if frmt[i] != '$': inc(i)
+      else: break
+    if i-1 >= start: add(result, substr(frmt, start, i - 1))
+
+
+proc defaultConfig*(): PStringTable =
+  ## creates a default configuration for HTML generation.
+  result = newStringTable(modeStyleInsensitive)
+  
+  template setConfigVar(key, val: expr) =
+    result[key] = val
+  
+  setConfigVar("split.item.toc", "20")
+  setConfigVar("doc.section", """
+<div class="section" id="$sectionID">
+<h1><a class="toc-backref" href="#$sectionTitleID">$sectionTitle</a></h1>
+<dl class="item">
+$content
+</dl></div>
+""")
+  setConfigVar("doc.section.toc", """
+<li>
+  <a class="reference" href="#$sectionID" id="$sectionTitleID">$sectionTitle</a>
+  <ul class="simple">
+    $content
+  </ul>
+</li>
+""")
+  setConfigVar("doc.item", """
+<dt id="$itemID"><pre>$header</pre></dt>
+<dd>
+$desc
+</dd>
+""")
+  setConfigVar("doc.item.toc", """
+  <li><a class="reference" href="#$itemID">$name</a></li>
+""")
+  setConfigVar("doc.toc", """
+<div class="navigation" id="navigation">
+<ul class="simple">
+$content
+</ul>
+</div>""")
+  setConfigVar("doc.body_toc", """
+$tableofcontents
+<div class="content" id="content">
+$moduledesc
+$content
+</div>
+""")
+  setConfigVar("doc.body_no_toc", "$moduledesc $content")
+  setConfigVar("doc.file", "$content")
+
+# ---------- forum ---------------------------------------------------------
+
+proc rstToHtml*(s: string, options: TRstParseOptions, 
+                config: PStringTable): string =
+  ## exported for *nimforum*.
+  
+  proc myFindFile(filename: string): string = 
+    # we don't find any files in online mode:
+    result = ""
+
+  const filen = "input"
+  var d: TRstGenerator
+  initRstGenerator(d, outHtml, config, filen, options, myFindFile, nil)
+  var dummyHasToc = false
+  var rst = rstParse(s, filen, 0, 1, dummyHasToc, options)
+  result = ""
+  renderRstToOut(d, rst, result)
   
\ No newline at end of file