1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
#!/usr/bin/env python3
from collections import namedtuple
import glob
import os
import os.path as osp
import jinja2
import yaml
CONVENTION_ATTRS = ["title", "description", "clang_format", "snippet"]
class Convention(namedtuple("Convention", CONVENTION_ATTRS)):
@staticmethod
def from_file(clang_format, file):
attrs = dict(description="", snippet="", clang_format={})
def jinja_expand(content):
template = jinja2.Template(content)
return template.render(cf=clang_format)
def cf_value(key):
data = clang_format
for key in key.split("."):
if key in data:
data = data[key]
return data
def is_comment(line):
return line.startswith("//")
def is_cf_data(line):
return line.startswith("// clang-format:")
def get_cf_data(line):
line = line[16:].lstrip()
fields = [t.strip() for t in line.split(":", 1)]
if len(fields) == 1:
fields = [fields[0], cf_value(fields[0])]
else:
fields[1] = yaml.safe_load(fields[1])
return dict([fields])
with open(file) as istr:
content = [line.rstrip() for line in istr.readlines()]
# retrieve title
assert is_comment(content[0])
attrs["title"] = content[0].lstrip("//").lstrip()
i = 1
# eat empty lines
while i < len(content) and not content[i]:
i += 1
# retrieve description
while i < len(content) and is_comment(content[i]):
if is_cf_data(content[i]):
attrs["clang_format"].update(get_cf_data(content[i]))
else:
attrs["description"] += content[i].lstrip("//").lstrip() + "\n"
i += 1
attrs["description"] = jinja_expand(attrs["description"])
# eat empty lines
while i < len(content) and not content[i]:
i += 1
# retrieve code snippet
while i < len(content):
if not content[i].lstrip().startswith("// clang-format"):
attrs["snippet"] += content[i] + "\n"
i += 1
basename = osp.splitext(osp.basename(file))[0]
data = clang_format
for key in basename.split("."):
if key in data:
data = data[key]
else:
break
else:
attrs["clang_format"][basename] = data
return Convention(**attrs)
def load_conventions(path):
with open("../../.clang-format") as istr:
clang_format = yaml.safe_load(istr)
assert osp.isdir(path)
for file in sorted(glob.glob(path + os.sep + "*.cpp")):
yield Convention.from_file(clang_format, file)
def build_documentation(template_str, ostr, **kwargs):
template = jinja2.Template(template_str)
template.stream(**kwargs).dump(ostr)
def main():
with open("README.md.jinja") as istr:
template_str = istr.read()
with open("README.md", "w") as ostr:
build_documentation(
template_str, ostr, conventions=list(load_conventions("snippets"))
)
if __name__ == "__main__":
main()
|