rstPreproc.nim
1 ## This is a tool for preprocessing rst files. Lines starting with ``%`` will 2 ## get substituted by nicely layouted nim and yaml code included from file in 3 ## the snippets tree. 4 ## 5 ## The syntax of substituted lines is ``'%' path '%' level``. *path* shall be 6 ## a path relative to the *snippets* directory. *level* shall be the level depth 7 ## of the first title that should be produced. 8 ## 9 ## Usage: 10 ## 11 ## rstPreproc -o:path <infile> 12 ## 13 ## *path* is the output path. If omitted, it will be equal to infile with its 14 ## suffix substituted by ``.rst``. *infile* is the source rst file. 15 ## 16 ## The reason for this complex approach is to have all snippets used in the docs 17 ## available as source files for automatic testing. This way, we can make sure 18 ## that the code in the docs actually works. 19 20 import parseopt, streams, tables, strutils, unicode, os, options, algorithm 21 22 var 23 infile = "" 24 path = none(string) 25 for kind, key, val in getopt(): 26 case kind 27 of cmdArgument: 28 if infile == "": 29 if key == "": 30 echo "invalid input file with empty name!" 31 quit 1 32 infile = key 33 else: 34 echo "Only one input file is supported!" 35 quit 1 36 of cmdLongOption, cmdShortOption: 37 case key 38 of "out", "o": 39 if path.isNone: path = some(val) 40 else: 41 echo "Duplicate output path!" 42 quit 1 43 else: 44 echo "Unknown option: ", key 45 quit 1 46 of cmdEnd: assert(false) # cannot happen 47 48 if infile == "": 49 echo "Missing input file!" 50 quit 1 51 52 if path.isNone: 53 for i in countdown(infile.len - 1, 0): 54 if infile[i] == '.': 55 if infile[i..^1] == ".rst": path = some(infile & ".rst") 56 else: path = some(infile[0..i] & "rst") 57 break 58 if path.isNone: path = some(infile & ".rst") 59 60 var tmpOut = newFileStream(path.get(), fmWrite) 61 62 proc append(s: string) = 63 tmpOut.writeLine(s) 64 65 const headingChars = ['=', '-', '^', ':', '\''] 66 67 proc outputExamples(curPath: string, level: int = 0) = 68 let titlePath = curPath / "title" 69 if fileExists(titlePath): 70 let titleFile = open(titlePath, fmRead) 71 defer: titleFile.close() 72 var title = "" 73 if titleFile.readLine(title): 74 let headingChar = if level >= headingChars.len: headingChars[^1] else: 75 headingChars[level] 76 append(title) 77 append(repeat(headingChar, title.len) & '\l') 78 79 # process content files under this directory 80 81 var subdirs = newSeq[string]() 82 var codeFiles = newSeq[string]() 83 for kind, filePath in walkDir(curPath, true): 84 if kind == pcFile: 85 if filePath != "title": codeFiles.add(filePath) 86 elif kind == pcDir: 87 subdirs.add(filePath) 88 codeFiles.sort() 89 case codeFiles.len 90 of 0: discard 91 of 1: 92 let (_, _, extension) = codeFiles[0].splitFile() 93 append(".. code:: " & extension[1..^1]) 94 append(" :file: " & (curPath / codeFiles[0]) & '\l') 95 of 2: 96 append(".. raw:: html") 97 append(" <table class=\"quickstart-example\"><thead><tr>") 98 for codeFile in codeFiles: 99 append(" <th>" & codeFile[3..^1] & "</th>") 100 append(" </th></tr></thead><tbody><tr><td>\n") 101 102 var first = true 103 for codeFile in codeFiles: 104 if first: first = false 105 else: append(".. raw:: html\n </td>\n <td>\n") 106 let (_, _, extension) = codeFile.splitFile() 107 append(".. code:: " & extension[1..^1]) 108 append(" :file: " & (curPath / codeFile) & '\l') 109 110 append(".. raw:: html") 111 append(" </td></tr></tbody></table>\n") 112 else: 113 echo "Unexpected number of files in ", curPath, ": ", codeFiles.len 114 115 # process child directories 116 117 subdirs.sort() 118 for dirPath in subdirs: 119 outputExamples(curPath / dirPath, level + 1) 120 121 var lineNum = 0 122 for line in infile.lines(): 123 if line.len > 0 and line[0] == '%': 124 var 125 srcPath = none(string) 126 level = 0 127 for i in 1..<line.len: 128 if line[i] == '%': 129 srcPath = some(line[1 .. i - 1]) 130 level = parseInt(line[i + 1 .. ^1]) 131 break 132 if srcPath.isNone: 133 echo "Second % missing in line " & $lineNum & "! content:\n" 134 echo line 135 quit 1 136 outputExamples("snippets" / srcPath.get(), level) 137 else: 138 append(line)