summaryrefslogtreecommitdiff
path: root/tools/makespec.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/makespec.py')
-rwxr-xr-xtools/makespec.py165
1 files changed, 165 insertions, 0 deletions
diff --git a/tools/makespec.py b/tools/makespec.py
new file mode 100755
index 0000000..84663f2
--- /dev/null
+++ b/tools/makespec.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+import re
+import sys
+from subprocess import *
+from string import Template
+
+if len(sys.argv) == 2:
+ specformat = sys.argv[1]
+ if not (specformat in ["html", "markdown"]):
+ sys.stderr.write("Format must be html or markdown\n")
+ exit(1)
+else:
+ sys.stderr.write("Usage: makespec.py [html|markdown]\n")
+ exit(1)
+
+def toIdentifier(s):
+ return re.sub(r'\s+', '-', re.sub(r'\W+', ' ', s.strip().lower()))
+
+def parseYaml(yaml):
+ metadata = {}
+ def parseField(match):
+ key = match.group(1)
+ val = match.group(2).strip()
+ if re.match(r'^\'', val):
+ val = val[1:len(val) - 1]
+ metadata[key] = val
+ fieldre = re.compile('^(\w+):(.*)$', re.MULTILINE)
+ re.sub(fieldre, parseField, yaml)
+ return metadata
+
+def pipe_through_prog(prog, text):
+ p1 = Popen(prog.split(), stdout=PIPE, stdin=PIPE, stderr=PIPE)
+ [result, err] = p1.communicate(input=text.encode('utf-8'))
+ return [p1.returncode, result.decode('utf-8'), err]
+
+def replaceAnchor(match):
+ refs.append("[{0}]: #{1}".format(match.group(1), match.group(2)))
+ if specformat == "html":
+ return '<a id="{1}" href="#{1}" class="definition">{0}</a>'.format(match.group(1), match.group(2))
+ else:
+ return match.group(0)
+
+stage = 0
+example = 0
+section = ""
+sections = []
+mdlines = []
+refs = []
+lastnum = []
+finishedMeta = False
+yamllines = []
+
+with open('spec.txt', 'r', encoding='utf-8') as spec:
+ for ln in spec:
+ if not finishedMeta:
+ yamllines.append(ln)
+ if re.match(r'^\.\.\.$', ln):
+ finishedMeta = True
+ elif re.match(r'^\.$', ln):
+ if stage == 0:
+ example += 1
+ mdlines.append("\n<div class=\"example\" id=\"example-{0}\" data-section=\"{1}\">\n".format(example, section))
+ mdlines.append("<div class=\"examplenum\"><a href=\"#example-{0}\">Example {0}</a>&nbsp;&nbsp;<a class=\"dingus\" title=\"open in interactive dingus\">(interact)</a></div>\n\n".format(example))
+ mdlines.append("````````````````````````````````````````````````````````` markdown\n")
+ stage = 1
+ elif stage == 1:
+ mdlines.append("`````````````````````````````````````````````````````````\n\n")
+ mdlines.append("````````````````````````````````````````````````````````` html\n")
+ stage = 2
+ elif stage == 2:
+ mdlines.append("`````````````````````````````````````````````````````````\n\n")
+ mdlines.append("</div>\n")
+ stage = 0
+ else:
+ sys.stderr.out("Encountered unknown stage {0}\n".format(stage))
+ sys.exit(1)
+ else:
+ if stage == 0:
+ match = re.match(r'^(#{1,6}) *(.*)', ln)
+ if match:
+ section = match.group(2)
+ lastlevel = len(lastnum)
+ level = len(match.group(1))
+ if re.search(r'{-}$', section):
+ section = re.sub(r' *{-} *$', '', section)
+ if specformat == 'html':
+ ln = re.sub(r' *{-} *$', '', ln)
+ number = ''
+ else:
+ if lastlevel == level:
+ lastnum[level - 1] = lastnum[level - 1] + 1
+ elif lastlevel < level:
+ while len(lastnum) < level:
+ lastnum.append(1)
+ else: # lastlevel > level
+ lastnum = lastnum[0:level]
+ lastnum[level - 1] = lastnum[level - 1] + 1
+ number = '.'.join([str(x) for x in lastnum])
+ ident = toIdentifier(section)
+ ln = re.sub(r' ', ' ' + number + ' ', ln, count=1)
+ sections.append(dict(level=level,
+ contents=section,
+ ident=ident,
+ number=number))
+ refs.append("[{0}]: #{1}".format(section, ident))
+ ln = re.sub(r'# +', '# <a id="{0}"></a> '.format(ident),
+ ln, count=1)
+ else:
+ ln = re.sub(r'\[([^]]*)\]\(@([^)]*)\)', replaceAnchor, ln)
+ else:
+ ln = re.sub(r' ', '␣', ln)
+ mdlines.append(ln)
+
+mdtext = ''.join(mdlines) + '\n\n' + '\n'.join(refs) + '\n'
+yaml = ''.join(yamllines)
+metadata = parseYaml(yaml)
+
+if specformat == "markdown":
+ sys.stdout.write(yaml + '\n\n' + mdtext)
+elif specformat == "html":
+ with open("template.html", "r", encoding="utf-8") as templatefile:
+ template = Template(templatefile.read())
+ toclines = []
+ for section in sections:
+ indent = ' ' * (section['level'] - 1)
+ toclines.append(indent + '* [' + section['number'] + ' ' +
+ section['contents'] + '](#' + section['ident'] + ')')
+ toc = '<div id="TOC">\n\n' + '\n'.join(toclines) + '\n\n</div>\n\n'
+ prog = "build/src/cmark"
+ [retcode, result, err] = pipe_through_prog(prog, toc + mdtext)
+ if retcode == 0:
+ result = re.sub(r'␣', '<span class="space"> </span>', result)
+ result = re.sub(r'<h([1-6])><a id="([^\"]*)"><\/a> ',
+ "<h\\1 id=\"\\2\">", result)
+ # put plural s inside links for better visuals:
+ result = re.sub(r'<\/a>s', "s</a>", result)
+ sys.stdout.write(template.substitute(metadata, body=result))
+
+ # check for errors:
+ idents = []
+ for ident in re.findall(r'id="([^"]*)"', result):
+ if ident in idents:
+ sys.stderr.write("WARNING: duplicate identifier '" + ident +
+ "'\n")
+ else:
+ idents.append(ident)
+ for href in re.findall(r'href="#([^"]*)"', result):
+ if not (href in idents):
+ sys.stderr.write("WARNING: internal link with no anchor '" +
+ href + "'\n")
+ reftexts = []
+ for ref in refs:
+ ref = re.sub('].*',']',ref).upper()
+ if ref in reftexts:
+ sys.stderr.write("WARNING: duplicate reference link '" +
+ ref + "'\n")
+ else:
+ reftexts.append(ref)
+
+ else:
+ sys.stderr.write("Error converting markdown version of spec:\n")
+ sys.stderr.write(err)
+ exit(1)
+
+exit(0)