aboutsummaryrefslogtreecommitdiff
path: root/makespec.py
blob: 07bf453573299f350300c3c56c0b36ef13eaed27 (plain)
  1. #!/usr/bin/env python3
  2. import re
  3. import sys
  4. from subprocess import *
  5. from string import Template
  6. if len(sys.argv) == 3:
  7. specfile = sys.argv[1]
  8. specformat = sys.argv[2]
  9. if not (specformat in ["html", "markdown"]):
  10. sys.stderr.write("Format must be html or markdown\n")
  11. exit(1)
  12. else:
  13. sys.stderr.write("Usage: makespec.py SPECFILE [html|markdown]\n")
  14. exit(1)
  15. def toIdentifier(s):
  16. return re.sub(r'\s+', '-', re.sub(r'\W+', ' ', s.strip().lower()))
  17. def parseYaml(yaml):
  18. metadata = {}
  19. def parseField(match):
  20. key = match.group(1)
  21. val = match.group(2).strip()
  22. if re.match(r'^\'', val):
  23. val = val[1:len(val) - 1]
  24. metadata[key] = val
  25. fieldre = re.compile('^(\w+):(.*)$', re.MULTILINE)
  26. re.sub(fieldre, parseField, yaml)
  27. return metadata
  28. def pipe_through_prog(prog, text):
  29. p1 = Popen(prog.split(), stdout=PIPE, stdin=PIPE, stderr=PIPE)
  30. [result, err] = p1.communicate(input=text.encode('utf-8'))
  31. return [p1.returncode, result.decode('utf-8'), err]
  32. def replaceAnchor(match):
  33. refs.append("[{0}]: #{1}".format(match.group(1), match.group(2)))
  34. if specformat == "html":
  35. return '<a id="{1}" href="#{1}" class="definition">{0}</a>'.format(match.group(1), match.group(2))
  36. else:
  37. return match.group(0)
  38. stage = 0
  39. example = 0
  40. section = ""
  41. sections = []
  42. mdlines = []
  43. refs = []
  44. lastnum = []
  45. finishedMeta = False
  46. yamllines = []
  47. with open(specfile, 'r', encoding='utf-8') as spec:
  48. for ln in spec:
  49. if not finishedMeta:
  50. yamllines.append(ln)
  51. if re.match(r'^\.\.\.$', ln):
  52. finishedMeta = True
  53. elif re.match(r'^\.$', ln):
  54. if stage == 0:
  55. example += 1
  56. mdlines.append("\n<div class=\"example\" id=\"example-{0}\" data-section=\"{1}\">\n".format(example, section))
  57. 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))
  58. mdlines.append("````````````````````````````````````````````````````````` markdown\n")
  59. stage = 1
  60. elif stage == 1:
  61. mdlines.append("`````````````````````````````````````````````````````````\n\n")
  62. mdlines.append("````````````````````````````````````````````````````````` html\n")
  63. stage = 2
  64. elif stage == 2:
  65. mdlines.append("`````````````````````````````````````````````````````````\n\n")
  66. mdlines.append("</div>\n")
  67. stage = 0
  68. else:
  69. sys.stderr.out("Encountered unknown stage {0}\n".format(stage))
  70. sys.exit(1)
  71. else:
  72. if stage == 0:
  73. match = re.match(r'^(#{1,6}) *(.*)', ln)
  74. if match:
  75. section = match.group(2)
  76. lastlevel = len(lastnum)
  77. level = len(match.group(1))
  78. if re.search(r'{-}$', section):
  79. section = re.sub(r' *{-} *$', '', section)
  80. number = ''
  81. else:
  82. if lastlevel == level:
  83. lastnum[level - 1] = lastnum[level - 1] + 1
  84. elif lastlevel < level:
  85. while len(lastnum) < level:
  86. lastnum.append(1)
  87. else: # lastlevel > level
  88. lastnum = lastnum[0:level]
  89. lastnum[level - 1] = lastnum[level - 1] + 1
  90. number = '.'.join([str(x) for x in lastnum])
  91. ident = toIdentifier(section)
  92. ln = re.sub(r' ', ' ' + number + ' ', ln, count=1)
  93. sections.append(dict(level=level,
  94. contents=section,
  95. ident=ident,
  96. number=number))
  97. refs.append("[{0}]: #{1}".format(section, ident))
  98. ln = re.sub(r'# +', '# <a id="{0}"></a> '.format(ident),
  99. ln, count=1)
  100. else:
  101. ln = re.sub(r'\[([^]]*)\]\(@([^)]*)\)', replaceAnchor, ln)
  102. else:
  103. ln = re.sub(r' ', '␣', ln)
  104. mdlines.append(ln)
  105. mdtext = ''.join(mdlines) + '\n\n' + '\n'.join(refs) + '\n'
  106. yaml = ''.join(yamllines)
  107. metadata = parseYaml(yaml)
  108. if specformat == "markdown":
  109. sys.stdout.write(yaml + '\n\n' + mdtext)
  110. elif specformat == "html":
  111. with open("template.html", "r", encoding="utf-8") as templatefile:
  112. template = Template(templatefile.read())
  113. toclines = []
  114. for section in sections:
  115. indent = ' ' * (section['level'] - 1)
  116. toclines.append(indent + '* [' + section['number'] + ' ' +
  117. section['contents'] + '](#' + section['ident'] + ')')
  118. toc = '<div id="TOC">\n\n' + '\n'.join(toclines) + '\n\n</div>\n\n'
  119. prog = "build/src/cmark"
  120. [retcode, result, err] = pipe_through_prog(prog, toc + mdtext)
  121. if retcode == 0:
  122. result = re.sub(r'␣', '<span class="space"> </span>', result)
  123. sys.stdout.write(template.substitute(metadata, body=result))
  124. else:
  125. sys.stderr.write("Error converting markdown version of spec:\n")
  126. sys.stderr.write(err)
  127. exit(1)
  128. exit(0)