summary refs log blame commit diff stats
path: root/lib/packages/docutils/rstgen.nim
blob: 5b0b6c6eebdce0c5077857f056e101bbfc89772c (plain) (tree)
1
2
3
4
5
6
7
8
9

 
                                  





                                                   

                                                                            







                                                                               
  
                                                                            


                                                                               
 
                                                                              
                       



                    

    
                                                         

                                         
 
                   
                

                             
                  
                                                              
 

                                   
                           
                                                                 
                        
                           
                 
                                                                          


                              
                     
                                  


                                                                                
                                           

                                                                             


                                                                                  
 
                                                
 



                                                                        
                                                                  


                    






                                                              
                                                                 
                                                                



                                                     
    
                                                                         
                                                                         



                                                                              





                                                                               
    
                                                                          


















                                                                               
                        


                                      
                            
                                                                     







                       
                       
          


                                                 
                                             
                           
 
                                             


                                                    
                                                            

                                                                  

                                                                             
                                                       

                                            





                              

                                            




                            

                                            
















                                    
                                                                           


                                  
 




                                                                     
                                                  
                
                            
                  



                                              
                 

                                                
 
                                                                     
                      
             
                     

                   
                     
                                  


                                                                  


                                                           
       
                                                                  

 
                                                           
                                     
                    
 
                                                  

                                            
                           
 
                                                  
                                                     

                                              
 


                                            
                                                                          

                                                                            
                                                                          


                                                                         
                        




                                              
 
                                                          

                                                                     
                                                                                








                                                                              















                                                                               
                                                         
                                              

                                                                         



                                                                      
    







                                                                              






                                                                               



                   
                                                                    
                     
                



                  
                                           





                                                
 
                             



                            
                          


                                        








                                                                             
                                                                          

                                 
                                                                  
 

                       
                                             
                                                                       


                   
                     

                   

                                                                          
 
                                                       

                                                  
                                                                         
                                                                       

                                                                         
                                                                             

                                          

                                                                       
                                               

                                           
 
                                

                                                          
                                                                              

                             



                                             
 
                                             

                                  
                                        
                                            
                                      
                                          
 
                                              
                                          
                


                 
                   

               
                               
                       








                                



                                              
                                                             

                  
                      

                                   
                                                            
                 
           
                        


                                                                                                       
                                           
             
                                                            
         
                                        

                                                                                

                                                        
                                                                   

                                 
                                                        
                                                        
                           


                              
                     







                                                                              















                                                                             
                                                 
       
                                                   

                  
                                                                 
                                                                        
             

                                                                          
                    
                    








                                                                              

                                                                



                                                         
                              


                                                                      
                   




                                 
                     
                               
         
                                                      
                                                                                 

                                                                       
         


                                                                          
 
                                                            



                                                                     
                                                             
                   

                      
                                                                    
                                     
                                                                               
 
                                                            



                                                                      
                                                             





                                                                          
                                          
 







                                                                
                                          
 
                               

                                                                              
    



                                                                            
                                                          

                                




                                                   

                                    








                                                    



                                                                             







                                                                    

                                       

                                                                           
                             
                         





                                                                            
                                            
               
                                               
           

                                                            

                                        

                                  



                                                                             
                                                                       




















                                                                               
                                                  

             




                                                




                                                  





                                                
                     



                                            
 

                                                                              


                                                         
                                                                            













                                                                              
                                                               

                                                                    
                        







                                                                

                               
                                 


                                       
 
                                                                
                                                                         
                                                                               
       
                                                        
                                                          
                               
                                      



                                                                                
                                  
 
                                                               


                                                     
                                        


                                                        
                                           


                                                                      
                          
                                                                       

                                                                
 
 
                                                               
                         
                                                                          

                                                                 
                                                                 
                                            
              
                             








                                          
                                                           


                                                                          
 
                                                            

                        

                
 


                                                               
 


                                                                 
 


                                                               
 






                                                      
 
                                                                        
 











                                                                 
                                                      
 
                                                             
                         
                               
                                                               

                                                        
 




                                                                             
                                 





                                                                          
                        
                                                                          



                                                                               


                                           




                                               


                                             

























                                                                               
                                                                         






                                                                               

                            
                            

                                                        
                                                          

          
                                       
                      
                                                                                                                        




                                       
                                             

                                                


                                                                   
 
                                                                






                                                                              
                                                                            

                              
                             
                                        

                           
 


                                                                              
                                                                     




                                                                           
                                                           
       
                           
                                   
               
                                  
                 

                              





                                                                                 

                                                        
                                                                

                                   
                                                
               


                                                                           

                                      

                                                

                                                            
               
                          

                                                            
                                                  
                                                 


                                     
                                                   




                                                  
 























                                                                            
                                    
                                       
                    


                                                                     
                                               
                                      

                                                       

                                       
                 

                                                          
                 
                                                   
             
                                        

                                                       
                                                             
                      
                                                  
                   
                                                                       
                   
                                                                    
                                                

                                    
                                      
                                                                   
                          
                                    
                 
                                                  
                     
                                                   

                                                           
                                                             


                                                          

                                                                           
                

                              

                                            







                                              
                     
                                                
                       
                                                          
             
                                                                   
                
                                                 
                
                                                          
           

                        


                                                            


                                                           





                                      


                                                           






                                             
 


                                                 
                                                 















                                                                           
                                                   




                                                            


                                                                          









                                                                               


                                                              


              

                                                                



                   

                      

                                        
             


                                   
             

                        
                  
                 
                   
                                                
                
                                                       
                                   
                                                                

                                     
                                            
                   
                   

                          
                                                                           
                                         
                    

                                     
                                                                           
             

                   

                             
                                                          





                                                        
             
                                                                           
           
                                                                            
                 
                




                                                            
                                       

                                                                  
                                                                             








                                                                               
                                               
 
                                   
                     
 

                                                                               
















                                                                                
                                                                      




                                  

                                                  













                                        


                                                                

                                                         
                                                             


                                                                            
                                                    
                                                 








                                                                               
                        






                                                                              
                                                             

                                            
                                             



                                             
                     
                                                                  
                                         



                                                          

 



                                            
#
#
#            Nim'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: (see http://docutils.sourceforge.net/rst.html for
## information on this markup syntax) and is used by the compiler's `docgen
## tools <docgen.html>`_.
##
## You can generate HTML output through the convenience proc ``rstToHtml``,
## which provided an input string with rst markup returns a string with the
## generated HTML. The final output is meant to be embedded inside a full
## document you provide yourself, so it won't contain the usual ``<header>`` or
## ``<body>`` parts.
##
## You can also create a ``RstGenerator`` structure and populate it with the
## other lower level methods to finally build complete documents. This requires
## many options and tweaking, but you are not limited to snippets and can
## generate `LaTeX documents <https://en.wikipedia.org/wiki/LaTeX>`_ too.

import strutils, os, hashes, strtabs, rstast, rst, highlite, tables, sequtils,
  algorithm, parseutils

const
  HtmlExt = "html"
  IndexExt* = ".idx"

type
  OutputTarget* = enum ## which document type to generate
    outHtml,            # output is HTML
    outLatex            # output is Latex

  TocEntry = object
    n*: PRstNode
    refname*, header*: string

  MetaEnum* = enum
    metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion

  RstGenerator* = object of RootObj
    target*: OutputTarget
    config*: StringTableRef
    splitAfter*: int          # split too long entries in the TOC
    listingCounter*: int
    tocPart*: seq[TocEntry]
    hasToc*: bool
    theIndex: string # Contents of the index file to be dumped at the end.
    options*: RstParseOptions
    findFile*: FindFileHandler
    msgHandler*: MsgHandler
    filename*: string
    meta*: array[MetaEnum, string]
    currentSection: string ## \
    ## Stores the empty string or the last headline/overline found in the rst
    ## document, so it can be used as a prettier name for term index generation.
    seenIndexTerms: Table[string, int] ## \
    ## Keeps count of same text index terms to generate different identifiers
    ## for hyperlinks. See renderIndexTerm proc for details.
    id*: int               ## A counter useful for generating IDs.
    onTestSnippet*: proc (d: var RstGenerator; filename, cmd: string; status: int;
                          content: string)

  PDoc = var RstGenerator ## Alias to type less.

  CodeBlockParams = object ## Stores code block params.
    numberLines: bool ## True if the renderer has to show line numbers.
    startLine: int ## The starting line of the code block, by default 1.
    langStr: string ## Input string used to specify the language.
    lang: SourceLanguage ## Type of highlighting, by default none.
    filename: string
    testCmd: string
    status: int

proc init(p: var CodeBlockParams) =
  ## Default initialisation of CodeBlockParams to sane values.
  p.startLine = 1
  p.lang = langNone
  p.langStr = ""

proc initRstGenerator*(g: var RstGenerator, target: OutputTarget,
                       config: StringTableRef, filename: string,
                       options: RstParseOptions,
                       findFile: FindFileHandler=nil,
                       msgHandler: MsgHandler=nil) =
  ## Initializes a ``RstGenerator``.
  ##
  ## You need to call this before using a ``RstGenerator`` with any other
  ## procs in this module. Pass a non ``nil`` ``StringTableRef`` value as
  ## `config` with parameters used by the HTML output generator.  If you don't
  ## know what to use, pass the results of the `defaultConfig()
  ## <#defaultConfig>_` proc.
  ##
  ## The `filename` parameter will be used for error reporting and creating
  ## index hyperlinks to the file, but you can pass an empty string here if you
  ## are parsing a stream in memory. If `filename` ends with the ``.nim``
  ## extension, the title for the document will be set by default to ``Module
  ## filename``.  This default title can be overriden by the embedded rst, but
  ## it helps to prettify the generated index if no title is found.
  ##
  ## The ``RstParseOptions``, ``FindFileHandler`` and ``MsgHandler`` types
  ## are defined in the the `packages/docutils/rst module <rst.html>`_.
  ## ``options`` selects the behaviour of the rst parser.
  ##
  ## ``findFile`` is a proc used by the rst ``include`` directive among others.
  ## The purpose of this proc is to mangle or filter paths. It receives paths
  ## specified in the rst document and has to return a valid path to existing
  ## files or the empty string otherwise.  If you pass ``nil``, a default proc
  ## will be used which given a path returns the input path only if the file
  ## exists. One use for this proc is to transform relative paths found in the
  ## document to absolute path, useful if the rst file and the resources it
  ## references are not in the same directory as the current working directory.
  ##
  ## The ``msgHandler`` is a proc used for user error reporting. It will be
  ## called with the filename, line, col, and type of any error found during
  ## parsing. If you pass ``nil``, a default message handler will be used which
  ## writes the messages to the standard output.
  ##
  ## Example:
  ##
  ## .. code-block:: nim
  ##
  ##   import packages/docutils/rstgen
  ##
  ##   var gen: RstGenerator
  ##   gen.initRstGenerator(outHtml, defaultConfig(), "filename", {})
  g.config = config
  g.target = target
  g.tocPart = @[]
  g.filename = filename
  g.splitAfter = 20
  g.theIndex = ""
  g.options = options
  g.findFile = findFile
  g.currentSection = ""
  g.id = 0
  let fileParts = filename.splitFile
  if fileParts.ext == ".nim":
    g.currentSection = "Module " & fileParts.name
  g.seenIndexTerms = initTable[string, int]()
  g.msgHandler = msgHandler

  let s = config.getOrDefault"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 RstGenerator, outfile: string) =
  ## Writes the current index buffer to the specified output file.
  ##
  ## You previously need to add entries to the index with the `setIndexTerm()
  ## <#setIndexTerm>`_ proc. If the index is empty the file won't be created.
  if g.theIndex.len > 0: writeFile(outfile, g.theIndex)

proc addXmlChar(dest: var string, c: char) =
  case c
  of '&': add(dest, "&amp;")
  of '<': add(dest, "&lt;")
  of '>': add(dest, "&gt;")
  of '\"': add(dest, "&quot;")
  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)

proc escChar*(target: OutputTarget, dest: var string, c: char) {.inline.} =
  case target
  of outHtml:  addXmlChar(dest, c)
  of outLatex: addTexChar(dest, c)

proc addSplitter(target: OutputTarget; dest: var string) {.inline.} =
  case target
  of outHtml: add(dest, "<wbr />")
  of outLatex: add(dest, "\\-")

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: discard
    inc(result)
  dec(result)                 # last valid index

proc esc*(target: OutputTarget, s: string, splitAfter = -1): string =
  ## Escapes the HTML.
  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
      addSplitter(target, result)
      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: OutputTarget, xml, tex: string): string =
  if target != outLatex: result = xml
  else: result = tex

proc dispF(target: OutputTarget, xml, tex: string,
           args: varargs[string]): string =
  if target != outLatex: result = xml % args
  else: result = tex % args

proc dispA(target: OutputTarget, dest: var string,
           xml, tex: string, args: varargs[string]) =
  if target != outLatex: addf(dest, xml, args)
  else: addf(dest, tex, args)

proc `or`(x, y: string): string {.inline.} =
  result = if x.isNil: y else: x

proc renderRstToOut*(d: var RstGenerator, n: PRstNode, result: var string)
  ## Writes into ``result`` the rst ast ``n`` using the ``d`` configuration.
  ##
  ## Before using this proc you need to initialise a ``RstGenerator`` with
  ## ``initRstGenerator`` and parse a rst file with ``rstParse`` from the
  ## `packages/docutils/rst module <rst.html>`_. Example:
  ##
  ## .. code-block:: nim
  ##
  ##   # ...configure gen and rst vars...
  ##   var generatedHTML = ""
  ##   renderRstToOut(gen, rst, generatedHTML)
  ##   echo generatedHTML

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 quoteIndexColumn(text: string): string =
  ## Returns a safe version of `text` for serialization to the ``.idx`` file.
  ##
  ## The returned version can be put without worries in a line based tab
  ## separated column text file. The following character sequence replacements
  ## will be performed for that goal:
  ##
  ## * ``"\\"`` => ``"\\\\"``
  ## * ``"\n"`` => ``"\\n"``
  ## * ``"\t"`` => ``"\\t"``
  result = text.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t")

proc unquoteIndexColumn(text: string): string =
  ## Returns the unquoted version generated by ``quoteIndexColumn``.
  result = text.replace("\\t", "\t").replace("\\n", "\n").replace("\\\\", "\\")

proc setIndexTerm*(d: var RstGenerator, id, term: string,
                   linkTitle, linkDesc = "") =
  ## Adds a `term` to the index using the specified hyperlink identifier.
  ##
  ## A new entry will be added to the index using the format
  ## ``term<tab>file#id``. The file part will come from the `filename`
  ## parameter used in a previous call to the `initRstGenerator()
  ## <#initRstGenerator>`_ proc.
  ##
  ## The `id` will be appended with a hash 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, see `Index (idx) file format <docgen.html#index-idx-file-format>`_
  ## for more information). Unlike other index terms, title entries are
  ## inserted at the beginning of the accumulated buffer to maintain a logical
  ## order of entries.
  ##
  ## 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>`_.
  var
    entry = term
    isTitle = false
  entry.add('\t')
  let htmlFile = changeFileExt(extractFilename(d.filename), HtmlExt)
  entry.add(htmlFile)
  if id.len > 0:
    entry.add('#')
    entry.add(id)
  else:
    isTitle = true
  if linkTitle.len > 0 or linkDesc.len > 0:
    entry.add('\t' & linkTitle.quoteIndexColumn)
    entry.add('\t' & linkDesc.quoteIndexColumn)
  entry.add("\n")

  if isTitle: d.theIndex.insert(entry)
  else: d.theIndex.add(entry)

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) =
  ## Renders the string decorated within \`foobar\`\:idx\: markers.
  ##
  ## Additionally adds the encosed text to the index as a term. Since we are
  ## interested in different instances of the same term to have different
  ## entries, a table is used to keep track of the amount of times a term has
  ## previously appeared to give a different identifier value for each.
  let refname = n.rstnodeToRefname
  if d.seenIndexTerms.hasKey(refname):
    d.seenIndexTerms[refname] = d.seenIndexTerms.getOrDefault(refname) + 1
  else:
    d.seenIndexTerms[refname] = 1
  let id = refname & '_' & $d.seenIndexTerms.getOrDefault(refname)

  var term = ""
  renderAux(d, n, term)
  setIndexTerm(d, id, term, d.currentSection)
  dispA(d.target, result, "<span id=\"$1\">$2</span>", "$2\\label{$1}",
        [id, term])

type
  IndexEntry = object
    keyword: string
    link: string
    linkTitle: string ## If not nil, contains a prettier text for the href
    linkDesc: string ## If not nil, the title attribute of the final href

  IndexedDocs = Table[IndexEntry, seq[IndexEntry]] ## \
    ## Contains the index sequences for doc types.
    ##
    ## The key is a *fake* IndexEntry which will contain the title of the
    ## document in the `keyword` field and `link` will contain the html
    ## filename for the document. `linkTitle` and `linkDesc` will be nil.
    ##
    ## The value indexed by this IndexEntry is a sequence with the real index
    ## entries found in the ``.idx`` file.

proc cmp(a, b: IndexEntry): int =
  ## Sorts two ``IndexEntry`` first by `keyword` field, then by `link`.
  result = cmpIgnoreStyle(a.keyword, b.keyword)
  if result == 0:
    result = cmpIgnoreStyle(a.link, b.link)

proc hash(x: IndexEntry): Hash =
  ## Returns the hash for the combined fields of the type.
  ##
  ## The hash is computed as the chained hash of the individual string hashes.
  assert(not x.keyword.isNil)
  assert(not x.link.isNil)
  result = x.keyword.hash !& x.link.hash
  result = result !& (x.linkTitle or "").hash
  result = result !& (x.linkDesc or "").hash
  result = !$result

proc `<-`(a: var IndexEntry, b: IndexEntry) =
  shallowCopy a.keyword, b.keyword
  shallowCopy a.link, b.link
  if b.linkTitle.isNil: a.linkTitle = ""
  else: shallowCopy a.linkTitle, b.linkTitle
  if b.linkDesc.isNil: a.linkDesc = ""
  else: shallowCopy a.linkDesc, b.linkDesc

proc sortIndex(a: var openArray[IndexEntry]) =
  # 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: IndexEntry
      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 escapeLink(s: string): string =
  result = newStringOfCap(s.len + s.len shr 2)
  for c in items(s):
    case c
    of 'a'..'z', '_', 'A'..'Z', '0'..'9', '.', '#', ',', '/':
      result.add c
    else:
      add(result, "%")
      add(result, toHex(ord(c), 2))

proc generateSymbolIndex(symbols: seq[IndexEntry]): string =
  result = "<dl>"
  var i = 0
  while i < symbols.len:
    let keyword = symbols[i].keyword
    let cleaned_keyword = keyword.escapeLink
    result.addf("<dt><a name=\"$2\" href=\"#$2\"><span>$1:</span></a></dt><dd><ul class=\"simple\">\n",
                [keyword, cleaned_keyword])
    var j = i
    while j < symbols.len and keyword == symbols[j].keyword:
      let
        url = symbols[j].link.escapeLink
        text = if not symbols[j].linkTitle.isNil: symbols[j].linkTitle else: url
        desc = if not symbols[j].linkDesc.isNil: symbols[j].linkDesc else: ""
      if desc.len > 0:
        result.addf("""<li><a class="reference external"
          title="$3" data-doc-search-tag="$2" href="$1">$2</a></li>
          """, [url, text, desc])
      else:
        result.addf("""<li><a class="reference external"
          data-doc-search-tag="$2" href="$1">$2</a></li>
          """, [url, text])
      inc j
    result.add("</ul></dd>\n")
    i = j
  result.add("</dl>")

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 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 = repeat("<li><ul>", newLevel - level)
  else:
    result = repeat("</ul></li>", level - newLevel)
  level = newLevel

proc generateDocumentationTOC(entries: seq[IndexEntry]): string =
  ## Returns the sequence of index entries in an HTML hierarchical list.
  result = ""
  # Build a list of levels and extracted titles to make processing easier.
  var
    titleRef: string
    titleTag: string
    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
      # Also, ignore the linkTitle and use directly the keyword.
      levels[L].text = entry.keyword
    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 = "<ul>\n"
  level = 1
  L = 0
  while L < entries.len:
    let link = entries[L].link
    if link.isDocumentationTitle:
      titleRef = link
      titleTag = levels[L].text
    else:
      result.add(level.indentToLevel(levels[L].level))
      result.addf("""<li><a class="reference" data-doc-search-tag="$1" href="$2">
        $3</a></li>
        """, [titleTag & " : " & levels[L].text, link, levels[L].text])
    inc L
  result.add(level.indentToLevel(1) & "</ul>\n")
  assert(not titleRef.isNil,
    "Can't use this proc on an API index, docs always have a title entry")

proc generateDocumentationIndex(docs: IndexedDocs): 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[IndexEntry, seq[IndexEntry]](docs))
  sort(titles, cmp)

  for title in titles:
    let tocList = generateDocumentationTOC(docs.getOrDefault(title))
    result.add("<ul><li><a href=\"" &
      title.link & "\">" & title.keyword & "</a>\n" & tocList & "</li></ul>\n")

proc generateDocumentationJumps(docs: IndexedDocs): string =
  ## Returns a plain list of hyperlinks to documentation TOCs in HTML.
  result = "Documents: "

  # Sort the titles to generate their toc in alphabetical order.
  var titles = toSeq(keys[IndexEntry, seq[IndexEntry]](docs))
  sort(titles, cmp)

  var chunks: seq[string] = @[]
  for title in titles:
    chunks.add("<a href=\"" & title.link & "\">" & title.keyword & "</a>")

  result.add(chunks.join(", ") & ".<br/>")

proc generateModuleJumps(modules: seq[string]): string =
  ## Returns a plain list of hyperlinks to the list of modules.
  result = "Modules: "

  var chunks: seq[string] = @[]
  for name in modules:
    chunks.add("<a href=\"" & name & ".html\">" & name & "</a>")

  result.add(chunks.join(", ") & ".<br/>")

proc readIndexDir(dir: string):
    tuple[modules: seq[string], symbols: seq[IndexEntry], docs: IndexedDocs] =
  ## Walks `dir` reading ``.idx`` files converting them in IndexEntry items.
  ##
  ## Returns the list of found module names, the list of free symbol entries
  ## and the different documentation indexes. The list of modules is sorted.
  ## See the documentation of ``mergeIndexes`` for details.
  result.modules = @[]
  result.docs = initTable[IndexEntry, seq[IndexEntry]](32)
  newSeq(result.symbols, 15_000)
  setLen(result.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[IndexEntry]
        title: IndexEntry
        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` trailing part.
        if title.keyword.isNil and fileEntries[F].link.isDocumentationTitle:
          title.keyword = fileEntries[F].keyword
          title.link = fileEntries[F].link

        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 = ""
          fileEntries[F].linkDesc = ""
        inc F
      # Depending on type add this to the list of symbols or table of APIs.
      if title.keyword.isNil:
        for i in 0 .. <F:
          # Don't add to symbols TOC entries (they start with a whitespace).
          let toc = fileEntries[i].linkTitle
          if not toc.isNil and toc.len > 0 and toc[0] == ' ':
            continue
          # Ok, non TOC entry, add it.
          setLen(result.symbols, L + 1)
          result.symbols[L] = fileEntries[i]
          inc L
        result.modules.add(path.splitFile.name)
      else:
        # Generate the symbolic anchor for index quickjumps.
        title.linkTitle = "doc_toc_" & $result.docs.len
        result.docs[title] = fileEntries

  sort(result.modules, system.cmp)

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 ``nim 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 (modules, symbols, docs) = readIndexDir(dir)

  result = ""
  # Generate a quick jump list of documents.
  if docs.len > 0:
    result.add(generateDocumentationJumps(docs))
    result.add("<p />")

  # Generate hyperlinks to all the linked modules.
  if modules.len > 0:
    result.add(generateModuleJumps(modules))
    result.add("<p />")

  # 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 symbols.len > 0:
    sortIndex(symbols)
    result.add("<h2>API symbols</h2>\n")
    result.add(generateSymbolIndex(symbols))


# ----------------------------------------------------------------------------

proc stripTOCHTML(s: string): string =
  ## Ugly quick hack to remove HTML tags from TOC titles.
  ##
  ## A TocEntry.header field already contains rendered HTML tags. Instead of
  ## implementing a proper version of renderRstToOut() which recursively
  ## renders an rst tree to plain text, we simply remove text found between
  ## angled brackets. Given the limited possibilities of rst inside TOC titles
  ## this should be enough.
  result = s
  var first = result.find('<')
  while first >= 0:
    let last = result.find('>', first)
    if last < 0:
      # Abort, since we didn't found a closing angled bracket.
      return
    result.delete(first, last)
    first = result.find('<', first)

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)
  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, "\n<h$1><a class=\"toc-backref\" " &
      "id=\"$2\" href=\"#$2\">$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, "\n<h$1 id=\"$2\">$3</h$1>",
                            "\\rsth$4{$3}\\label{$2}\n", [
        $n.level, refname, tmp,
        $chr(n.level - 1 + ord('A'))])

  # Generate index entry using spaces to indicate TOC level for the output HTML.
  assert n.level >= 0
  setIndexTerm(d, refname, tmp.stripTOCHTML,
    spaces(max(0, n.level)) & tmp)

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])
    d.currentSection = 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])
    d.currentSection = d.meta[metaSubtitle]
  else:
    var tmp = ""
    for i in countup(0, len(n) - 1): renderRstToOut(d, n.sons[i], tmp)
    d.currentSection = 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: TocEntry, 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: 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)
    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) =
  let
    arg = getArgument(n)
  var
    options = ""

  var s = esc(d.target, getFieldValue(n, "scale").strip())
  if s.len > 0:
    dispA(d.target, options, " scale=\"$1\"", " scale=$1", [s])

  s = esc(d.target, getFieldValue(n, "height").strip())
  if s.len > 0:
    dispA(d.target, options, " height=\"$1\"", " height=$1", [s])

  s = esc(d.target, getFieldValue(n, "width").strip())
  if s.len > 0:
    dispA(d.target, options, " width=\"$1\"", " width=$1", [s])

  s = esc(d.target, getFieldValue(n, "alt").strip())
  if s.len > 0:
    dispA(d.target, options, " alt=\"$1\"", "", [s])

  s = esc(d.target, getFieldValue(n, "align").strip())
  if s.len > 0:
    dispA(d.target, options, " align=\"$1\"", "", [s])

  if options.len > 0: options = dispF(d.target, "$1", "[$1]", [options])

  var htmlOut = ""
  if arg.endsWith(".mp4") or arg.endsWith(".ogg") or
     arg.endsWith(".webm"):
    htmlOut = """
      <video src="$1"$2 autoPlay='true' loop='true' muted='true'>
      Sorry, your browser doesn't support embedded videos
      </video>
    """
  else:
    htmlOut = "<img src=\"$1\"$2/>"
  dispA(d.target, result, htmlOut, "\\includegraphics$2{$1}",
        [esc(d.target, arg), 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="$1" width="15"
        height="17" hspace="2" vspace="2" class="smiley" />""",
    "\\includegraphics{$1}",
    [d.config.getOrDefault"doc.smiley_format" % n.text])

proc parseCodeBlockField(d: PDoc, n: PRstNode, params: var CodeBlockParams) =
  ## Parses useful fields which can appear before a code block.
  ##
  ## This supports the special ``default-language`` internal string generated
  ## by the ``rst`` module to communicate a specific default language.
  case n.getArgument.toLowerAscii
  of "number-lines":
    params.numberLines = true
    # See if the field has a parameter specifying a different line than 1.
    var number: int
    if parseInt(n.getFieldValue, number) > 0:
      params.startLine = number
  of "file", "filename":
    # The ``file`` option is a Nim extension to the official spec, it acts
    # like it would for other directives like ``raw`` or ``cvs-table``. This
    # field is dealt with in ``rst.nim`` which replaces the existing block with
    # the referenced file, so we only need to ignore it here to avoid incorrect
    # warning messages.
    params.filename = n.getFieldValue.strip
  of "test":
    params.testCmd = n.getFieldValue.strip
    if params.testCmd.len == 0:
      params.testCmd = "nim c -r $1"
    else:
      params.testCmd = unescape(params.testCmd)
  of "status", "exitcode":
    var status: int
    if parseInt(n.getFieldValue, status) > 0:
      params.status = status
  of "default-language":
    params.langStr = n.getFieldValue.strip
    params.lang = params.langStr.getSourceLanguage
  else:
    d.msgHandler(d.filename, 1, 0, mwUnsupportedField, n.getArgument)

proc parseCodeBlockParams(d: PDoc, n: PRstNode): CodeBlockParams =
  ## Iterates over all code block fields and returns processed params.
  ##
  ## Also processes the argument of the directive as the default language. This
  ## is done last so as to override any internal communication field variables.
  result.init
  if n.isNil:
    return
  assert n.kind == rnCodeBlock
  assert(not n.sons[2].isNil)

  # Parse the field list for rendering parameters if there are any.
  if not n.sons[1].isNil:
    for son in n.sons[1].sons: d.parseCodeBlockField(son, result)

  # Parse the argument and override the language.
  result.langStr = strip(getArgument(n))
  if result.langStr != "":
    result.lang = getSourceLanguage(result.langStr)

proc buildLinesHTMLTable(d: PDoc; params: CodeBlockParams, code: string):
    tuple[beginTable, endTable: string] =
  ## Returns the necessary tags to start/end a code block in HTML.
  ##
  ## If the numberLines has not been used, the tags will default to a simple
  ## <pre> pair. Otherwise it will build a table and insert an initial column
  ## with all the line numbers, which requires you to pass the `code` to detect
  ## how many lines have to be generated (and starting at which point!).
  inc d.listingCounter
  let id = $d.listingCounter
  if not params.numberLines:
    result = (d.config.getOrDefault"doc.listing_start" %
                [id, sourceLanguageToStr[params.lang]],
              d.config.getOrDefault"doc.listing_end" % id)
    return

  var codeLines = code.strip.countLines
  assert codeLines > 0
  result.beginTable = """<table class="line-nums-table"><tbody><tr><td class="blob-line-nums"><pre class="line-nums">"""
  var line = params.startLine
  while codeLines > 0:
    result.beginTable.add($line & "\n")
    line.inc
    codeLines.dec
  result.beginTable.add("</pre></td><td>" & (
      d.config.getOrDefault"doc.listing_start" %
        [id, sourceLanguageToStr[params.lang]]))
  result.endTable = (d.config.getOrDefault"doc.listing_end" % id) &
      "</td></tr></tbody></table>" & (
      d.config.getOrDefault"doc.listing_button" % id)

proc renderCodeBlock(d: PDoc, n: PRstNode, result: var string) =
  ## Renders a code block, appending it to `result`.
  ##
  ## If the code block uses the ``number-lines`` option, a table will be
  ## generated with two columns, the first being a list of numbers and the
  ## second the code block itself. The code block can use syntax highlighting,
  ## which depends on the directive argument specified by the rst input, and
  ## may also come from the parser through the internal ``default-language``
  ## option to differentiate between a plain code block and Nim's code block
  ## extension.
  assert n.kind == rnCodeBlock
  if n.sons[2] == nil: return
  var params = d.parseCodeBlockParams(n)
  var m = n.sons[2].sons[0]
  assert m.kind == rnLeaf

  if params.testCmd.len > 0 and d.onTestSnippet != nil:
    d.onTestSnippet(d, params.filename, params.testCmd, params.status, m.text)

  let (blockStart, blockEnd) = buildLinesHTMLTable(d, params, m.text)

  dispA(d.target, result, blockStart, "\\begin{rstpre}\n", [])
  if params.lang == langNone:
    if len(params.langStr) > 0:
      d.msgHandler(d.filename, 1, 0, mwUnsupportedLanguage, params.langStr)
    for letter in m.text: escChar(d.target, result, letter)
  else:
    var g: GeneralTokenizer
    initGeneralTokenizer(g, m.text)
    while true:
      getNextToken(g, params.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, blockEnd, "\n\\end{rstpre}\n")

proc renderContainer(d: PDoc, n: PRstNode, result: var string) =
  var tmp = ""
  renderRstToOut(d, n.sons[2], tmp)
  var arg = esc(d.target, 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 or
       cmpIgnoreStyle(fieldname, "authors") == 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], result)
        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(ValueError, "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(ValueError, "unknown substitution var: " & id)
      of '{':
        var id = ""
        inc(i)
        while frmt[i] != '}':
          if frmt[i] == '\0':
            raise newException(ValueError, "'}' 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(ValueError, "unknown substitution var: " & id)
      else:
        raise newException(ValueError, "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*(): StringTableRef =
  ## Returns a default configuration for embedded HTML generation.
  ##
  ## The returned ``StringTableRef`` contains the parameters used by the HTML
  ## engine to build the final output. For information on what these parameters
  ## are and their purpose, please look up the file ``config/nimdoc.cfg``
  ## bundled with the compiler.
  ##
  ## The only difference between the contents of that file and the values
  ## provided by this proc is the ``doc.file`` variable. The ``doc.file``
  ## variable of the configuration file contains HTML to build standalone
  ## pages, while this proc returns just the content for procs like
  ## ``rstToHtml`` to generate the bare minimum HTML.
  result = newStringTable(modeStyleInsensitive)

  template setConfigVar(key, val) =
    result[key] = val

  # If you need to modify these values, it might be worth updating the template
  # file in config/nimdoc.cfg.
  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"><a name="$itemSymOrIDEnc"></a><pre>$header</pre></dt>
<dd>
$desc
</dd>
""")
  setConfigVar("doc.item.toc", """
  <li><a class="reference" href="#$itemSymOrIDEnc"
    title="$header_plain">$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.listing_start", "<pre class = \"listing\">")
  setConfigVar("doc.listing_end", "</pre>")
  setConfigVar("doc.listing_button", "</pre>")
  setConfigVar("doc.body_no_toc", "$moduledesc $content")
  setConfigVar("doc.file", "$content")
  setConfigVar("doc.smiley_format", "/images/smilies/$1.gif")

# ---------- forum ---------------------------------------------------------

proc rstToHtml*(s: string, options: RstParseOptions,
                config: StringTableRef): string =
  ## Converts an input rst string into embeddable HTML.
  ##
  ## This convenience proc parses any input string using rst markup (it doesn't
  ## have to be a full document!) and returns an embeddable piece of HTML. The
  ## proc is meant to be used in *online* environments without access to a
  ## meaningful filesystem, and therefore rst ``include`` like directives won't
  ## work. For an explanation of the ``config`` parameter see the
  ## ``initRstGenerator`` proc. Example:
  ##
  ## .. code-block:: nim
  ##   import packages/docutils/rstgen, strtabs
  ##
  ##   echo rstToHtml("*Hello* **world**!", {},
  ##     newStringTable(modeStyleInsensitive))
  ##   # --> <em>Hello</em> <strong>world</strong>!
  ##
  ## If you need to allow the rst ``include`` directive or tweak the generated
  ## output you have to create your own ``RstGenerator`` with
  ## ``initRstGenerator`` and related procs.

  proc myFindFile(filename: string): string =
    # we don't find any files in online mode:
    result = ""

  const filen = "input"
  var d: RstGenerator
  initRstGenerator(d, outHtml, config, filen, options, myFindFile,
                   rst.defaultMsgHandler)
  var dummyHasToc = false
  var rst = rstParse(s, filen, 0, 1, dummyHasToc, options)
  result = ""
  renderRstToOut(d, rst, result)


when isMainModule:
  assert rstToHtml("*Hello* **world**!", {},
    newStringTable(modeStyleInsensitive)) ==
    "<em>Hello</em> <strong>world</strong>!"