#!/usr/bin/env python3 """ A simple documentation generator generating Markdown-documented examples from annotated source code """ from pyparsing import * # tell pyparsing to never ignore white space characters ParserElement.setDefaultWhitespaceChars('') def parseTaggedContent(keyname, action, open="[[", close="]]", endTag="/"): """ Match content between [[keyname]] and [[/keyname]] and apply the action on it """ start = LineStart() + ZeroOrMore(" ") + "//" + ZeroOrMore(" ") + (open + str(keyname) + close) + LineEnd() end = LineStart() + ZeroOrMore(" ") + "//" + ZeroOrMore(" ") + (open + endTag + str(keyname) + close) + LineEnd() return start.suppress() + SkipTo(end).setParseAction(action) + end.suppress() def createMarkdownCode(markDownToken): """ Put code into Markdown syntax for code blocks with syntax highlighting """ def action(token): # only print code snippets with content if not token[0].rstrip(): return "" else: return "\n```" + markDownToken + "\n" + token[0].rstrip() + "\n```\n\n" return action def cppRules(): """ Define a list of rules to apply for cpp source code """ suppressHeader = Suppress(Combine("// -*-" + SkipTo("*******/" + LineEnd(), include=True))) suppressHeaderGuard = Suppress("#ifndef" + Optional(restOfLine) + LineEnd() + "#define" + Optional(restOfLine)) suppressEndHeaderGuard = Suppress("#endif" + Optional(restOfLine)) # make a code block (possibly containing comments) between [[codeblock]] and [[/codeblock]] createCppBlock = createMarkdownCode("cpp") parseCodeblock = parseTaggedContent("codeblock", action=createCppBlock) # treat doc and code line parseDoc = LineStart() + Suppress(ZeroOrMore(" ") + "//" + ZeroOrMore(" ")) + Optional(restOfLine) parseCode = LineStart() + ~(ZeroOrMore(" ") + "//") + (SkipTo(parseDoc) | SkipTo(StringEnd())) parseCode.setParseAction(createCppBlock) docTransforms = parseCodeblock | parseDoc | parseCode return [suppressHeader, suppressHeaderGuard, suppressEndHeaderGuard, docTransforms] def transformCode(code, rules, codeFileName): # exclude stuff between [[exclude]] and [[/exclude]] exclude = parseTaggedContent("exclude", action=replaceWith("")) code = exclude.transformString(code) # Enable toggling content between [[content]] and [[/content]] def wrapContentIntoDetails(token): beginDetails = "//\n// <details open>\n" summmary = "// <summary><b>Click to hide/show the file documentation</b> (or inspect the [source code]({}))</summary>\n//\n".format(codeFileName) endDetails = "\n//\n// </details>\n" return beginDetails + summmary + token[0] + endDetails wrapContent = parseTaggedContent("content", action=wrapContentIntoDetails) code = wrapContent.transformString(code) # Transform "[[details]] content" and "[[/details]]" to HTML transformDetailsBegin = LineStart() + Suppress(ZeroOrMore(" ") + "//" + ZeroOrMore(" ") + "[[details]]" + ZeroOrMore(" ")) + Optional(restOfLine) def detailBeginHTML(token): return "// <details><summary> Click to show " + token[0] + "</summary>\n" transformDetailsBegin.setParseAction(detailBeginHTML) code = transformDetailsBegin.transformString(code) transformDetailsEnd = LineStart() + Suppress(ZeroOrMore(" ") + "//" + ZeroOrMore(" ") + "[[/details]]") + Optional(restOfLine) transformDetailsEnd.setParseAction(replaceWith("// </details>\n")) code = transformDetailsEnd.transformString(code) for transform in rules: code = transform.transformString(code) return code