3 # This script converts API YAML files into markdown documentation
4 # and prints it to standard out.
6 from datetime import datetime
10 def escape(text, spaces=False):
12 Escapes text for safe inclusion in groff documents.
13 - Escapes backslashes as '\\e'
14 - Escapes periods at the start of a line as '\\&.'
17 for line in text.splitlines():
19 line = line.replace("\\", r"\[rs]")
20 # Escape leading period or apostrophe (groff macro triggers)
21 if line.startswith('.'):
23 elif line.startswith("'"):
26 line = line.replace(' ', '\\ ')
27 escaped_lines.append(line)
28 return '\n'.join(escaped_lines)
30 def arg_signature(name, arg):
33 sig = sig + ": " + arg["type"]
35 sig = sig + " = " + arg["default"]
38 def get_signature(name, info):
40 return ".BI " +escape(f'{name} : {info["type"]}', spaces=True)
41 sig = f'{name} : func('
43 sig += ", ".join(arg_signature(name,arg) for name,arg in info["args"].items())
44 sig += " -> " + info["return"].get("type", "Void")
46 sig += "-> " + info["return"].get("type", "Void")
48 return ".BI " + escape(sig, spaces=True)
51 .\\" Copyright (c) {year} Bruce Hill
52 .\\" All rights reserved.
54 .TH {name} 3 {date} "Tomo man-pages"
72 Name Type Description Default'''
74 arg_prefix_no_default = '''
79 Name Type Description'''
81 type_template = ''''\\" t
82 .\\" Copyright (c) {year} Bruce Hill
83 .\\" All rights reserved.
85 .TH {type} 3 {date} "Tomo man-pages"
87 {type} \\- a Tomo type
95 def markdown_to_roff(text):
96 text = text.replace('\n', ' ')
97 text = re.sub(r'`([^`]*)`', '\\\\fB\\1\\\\fR', text)
100 def write_method(path, name, info):
102 year = datetime.now().strftime("%Y")
103 date = datetime.now().strftime("%Y-%m-%d")
104 signature = get_signature(name, info)
105 lines.append(template.format(year=year, date=date, name=name, short=info["short"], description=info["description"], signature=signature))
107 if "args" in info and info["args"]:
108 lines.append(".SH ARGUMENTS")
109 has_defaults = any('default' in a for a in info['args'].values())
110 lines.append(arg_prefix if has_defaults else arg_prefix_no_default)
111 for arg,arg_info in info["args"].items():
113 default = escape(arg_info['default'], spaces=True) if 'default' in arg_info else '-'
114 description = markdown_to_roff(arg_info['description'])
115 lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}\t{default}")
117 description = markdown_to_roff(arg_info['description'])
118 lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}")
122 lines.append(".SH RETURN")
123 lines.append(info['return'].get('description', 'Nothing.'))
126 lines.append(".SH NOTES")
127 lines.append(info["note"])
130 lines.append(".SH ERRORS")
131 lines.append(info["errors"])
133 if "example" in info:
134 lines.append(".SH EXAMPLES")
136 lines.append(escape(info['example']))
140 type,_ = name.split(".")
141 lines.append(".SH SEE ALSO")
142 lines.append(f".BR Tomo-{type} (3)")
144 to_write = '\n'.join(lines) + '\n'
146 with open(path, "r") as f:
148 if to_write.splitlines()[5:] == existing.splitlines()[5:]:
150 except FileNotFoundError:
153 with open(path, "w") as f:
155 print(f"Updated {path}")
157 fn_summary_template = '''
163 .BR Tomo-{type}.{name} (3)
166 def fn_summary(type, name, info) -> str:
167 signature = get_signature(type+"."+name, info)
168 return fn_summary_template.format(
172 description=markdown_to_roff(info["description"]),
175 def write_type_manpage(path, type, methods):
176 year = datetime.now().strftime("%Y")
177 date = datetime.now().strftime("%Y-%m-%d")
178 method_summaries = [fn_summary(type, name, methods[name]) for name in sorted(methods.keys())]
179 type_manpage = type_template.format(
183 methods='\n'.join(method_summaries),
187 with open(path, "r") as f:
189 if type_manpage.splitlines()[5:] == existing.splitlines()[5:]:
191 except FileNotFoundError:
194 with open(path, "w") as f:
195 f.write(type_manpage)
196 print(f"Updated {path}")
199 def convert_to_markdown(yaml_doc:str)->str:
200 data = yaml.safe_load(yaml_doc)
203 for name,info in data.items():
204 write_method(f"man/man3/tomo-{name}.3", name, data[name])
206 type,fn = name.split(".")
207 if type not in types:
209 types[type][fn] = info
211 for type,methods in types.items():
212 write_type_manpage(f"man/man3/tomo-{type}.3", type, methods)
214 if __name__ == "__main__":
216 if len(sys.argv) > 1:
218 for filename in sys.argv[1:]:
219 with open(filename, "r") as f:
220 all_files += f.read()
221 convert_to_markdown(all_files)
223 convert_to_markdown(sys.stdin.read())