code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(223 lines)
1 #!/bin/env python3
3 # This script converts API YAML files into markdown documentation
4 # and prints it to standard out.
6 from datetime import datetime
7 import re
8 import yaml
10 def escape(text, spaces=False):
11 """
12 Escapes text for safe inclusion in groff documents.
13 - Escapes backslashes as '\\e'
14 - Escapes periods at the start of a line as '\\&.'
15 """
16 escaped_lines = []
17 for line in text.splitlines():
18 # Escape backslashes
19 line = line.replace("\\", r"\[rs]")
20 # Escape leading period or apostrophe (groff macro triggers)
21 if line.startswith('.'):
22 line = r'\&' + line
23 elif line.startswith("'"):
24 line = r'\&' + line
25 if spaces:
26 line = line.replace(' ', '\\ ')
27 escaped_lines.append(line)
28 return '\n'.join(escaped_lines)
30 def arg_signature(name, arg):
31 sig = name
32 if "type" in arg:
33 sig = sig + ": " + arg["type"]
34 if "default" in arg:
35 sig = sig + " = " + arg["default"]
36 return sig
38 def get_signature(name, info):
39 if "type" in info:
40 return ".BI " +escape(f'{name} : {info["type"]}', spaces=True)
41 sig = f'{name} : func('
42 if info["args"]:
43 sig += ", ".join(arg_signature(name,arg) for name,arg in info["args"].items())
44 sig += " -> " + info["return"].get("type", "Void")
45 else:
46 sig += "-> " + info["return"].get("type", "Void")
47 sig += ')'
48 return ".BI " + escape(sig, spaces=True)
50 template = ''''\\" t
51 .\\" Copyright (c) {year} Bruce Hill
52 .\\" All rights reserved.
53 .\\"
54 .TH {name} 3 {date} "Tomo man-pages"
55 .SH NAME
56 {name} \\- {short}
57 .SH LIBRARY
58 Tomo Standard Library
59 .SH SYNOPSIS
60 .nf
61 {signature}
62 .fi
63 .SH DESCRIPTION
64 {description}
65 '''
67 arg_prefix = '''
68 .TS
69 allbox;
70 lb lb lbx lb
71 l l l l.
72 Name Type Description Default'''
74 arg_prefix_no_default = '''
75 .TS
76 allbox;
77 lb lb lbx
78 l l l.
79 Name Type Description'''
81 type_template = ''''\\" t
82 .\\" Copyright (c) {year} Bruce Hill
83 .\\" All rights reserved.
84 .\\"
85 .TH {type} 3 {date} "Tomo man-pages"
86 .SH NAME
87 {type} \\- a Tomo type
88 .SH LIBRARY
89 Tomo Standard Library
90 .fi
91 .SH METHODS
92 {methods}
93 '''
95 def markdown_to_roff(text):
96 text = text.replace('\n', ' ')
97 text = re.sub(r'`([^`]*)`', '\\\\fB\\1\\\\fR', text)
98 return text
100 def write_method(path, name, info):
101 lines = []
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():
112 if has_defaults:
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}")
116 else:
117 description = markdown_to_roff(arg_info['description'])
118 lines.append(f"{arg}\t{arg_info.get('type', '')}\t{description}")
119 lines.append(".TE")
121 if "return" in info:
122 lines.append(".SH RETURN")
123 lines.append(info['return'].get('description', 'Nothing.'))
125 if "note" in info:
126 lines.append(".SH NOTES")
127 lines.append(info["note"])
129 if "errors" in info:
130 lines.append(".SH ERRORS")
131 lines.append(info["errors"])
133 if "example" in info:
134 lines.append(".SH EXAMPLES")
135 lines.append(".EX")
136 lines.append(escape(info['example']))
137 lines.append(".EE")
139 if "." in name:
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'
145 try:
146 with open(path, "r") as f:
147 existing = f.read()
148 if to_write.splitlines()[5:] == existing.splitlines()[5:]:
149 return
150 except FileNotFoundError:
151 pass
153 with open(path, "w") as f:
154 f.write(to_write)
155 print(f"Updated {path}")
157 fn_summary_template = '''
158 .TP
159 {signature}
160 {description}
162 For more, see:
163 .BR Tomo-{type}.{name} (3)
164 '''
166 def fn_summary(type, name, info) -> str:
167 signature = get_signature(type+"."+name, info)
168 return fn_summary_template.format(
169 type=type,
170 name=name,
171 signature=signature,
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(
180 year=year,
181 date=date,
182 type=type,
183 methods='\n'.join(method_summaries),
186 try:
187 with open(path, "r") as f:
188 existing = f.read()
189 if type_manpage.splitlines()[5:] == existing.splitlines()[5:]:
190 return
191 except FileNotFoundError:
192 pass
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)
202 types = {}
203 for name,info in data.items():
204 write_method(f"man/man3/tomo-{name}.3", name, data[name])
205 if "." in name:
206 type,fn = name.split(".")
207 if type not in types:
208 types[type] = {}
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__":
215 import sys
216 if len(sys.argv) > 1:
217 all_files = ""
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)
222 else:
223 convert_to_markdown(sys.stdin.read())