6
6
import os
7
7
import argparse
8
8
import sys
9
+ import shutil
9
10
10
11
parser = argparse .ArgumentParser ()
11
12
parser .add_argument (
18
19
"top-level headings start with a single `#`, sub-headings start with `##`, etc. For example, "
19
20
"3 would start with `###`, sub-headings would be `####`, etc." ,
20
21
)
22
+ parser .add_argument (
23
+ "-m" ,
24
+ "--multi-file" ,
25
+ action = "store_true" ,
26
+ help = "Write to one markdown file per section, rather than one single large file" ,
27
+ )
21
28
parser .add_argument (
22
29
"-o" ,
23
30
"--output" ,
24
31
type = str ,
25
- help = "Specify output file; specifing a - or omitting outputs to stdout" ,
32
+ help = "Specify output file (or directory, when using -m); specifing a - or omitting outputs to "
33
+ "stdout, but is not accepted in multi-file (-m) mode." ,
26
34
)
27
35
parser .add_argument (
28
36
"-W" ,
34
42
args = parser .parse_args ()
35
43
36
44
out_file = False
37
- if args .output and args .output != '-' :
45
+ if args .multi_file and (not args .output or args .output == '-' ):
46
+ print ("-o must specify a directory when using -m" )
47
+ sys .exit (1 )
48
+
49
+ if not args .multi_file and args .output and args .output != '-' :
38
50
if not args .overwrite and os .path .exists (args .output ):
39
51
print (f"{ args .output } already exists; remove it first or use --overwrite/-W to overwrite" )
40
52
sys .exit (1 )
@@ -71,20 +83,23 @@ def read_snippet(markdown, depth=args.markdown_level):
71
83
return None
72
84
73
85
74
- section_list , sections = [], {}
86
+ section_list , section_names , section_snips , sections = [], [], [], {}
75
87
for name , bp in app .blueprints .items ():
76
88
s = []
77
89
section_list .append (s )
90
+ section_names .append (name )
78
91
sections [name ] = s
79
92
snip = read_snippet (f'{ name } .md' )
80
93
if snip :
81
94
s .append (snip )
82
95
else :
83
96
s .append (f"{ h1 } { name .title ()} \n \n " )
84
97
app .logger .warning (f"{ name } .md not found: adding stub '{ name .title ()} ' section" )
98
+ section_snips .append (snip )
85
99
86
100
# Last section is for anything with a not found category:
87
101
section_list .append ([])
102
+ section_names .append ('uncategorized' )
88
103
89
104
90
105
# Sort endpoints within a section by the number of URL parts, first, then alphabetically because we
@@ -95,6 +110,8 @@ def endpoint_sort_key(rule):
95
110
96
111
for rule in sorted (app .url_map .iter_rules (), key = endpoint_sort_key ):
97
112
ep = rule .endpoint
113
+ if ep == 'static' :
114
+ continue
98
115
methods = [m for m in rule .methods if m not in ('OPTIONS' , 'HEAD' )]
99
116
if not methods :
100
117
app .logger .warning (f"Endpoint { ep } has no useful method, skipping!" )
@@ -109,6 +126,11 @@ def endpoint_sort_key(rule):
109
126
handler = app .view_functions [ep ]
110
127
111
128
doc = handler .__doc__
129
+ if ep .startswith ('legacy' ):
130
+ # We deliberately omit legacy endpoint documentation
131
+ if doc is not None :
132
+ app .logger .warning (f"Legacy endpoint { ep } has docstring but it will be omitted" )
133
+ continue
112
134
if doc is None :
113
135
app .logger .warning (f"Endpoint { ep } has no docstring!" )
114
136
doc = '*(undocumented)*'
@@ -174,7 +196,9 @@ def endpoint_sort_key(rule):
174
196
argdoc = argdoc .replace ('\n ' , '\n ' )
175
197
176
198
if ':`' in argdoc :
177
- app .logger .warning (f"{ arg } still contains some rst crap we need to handle" )
199
+ app .logger .warning (
200
+ f"{ method } { url } ({ arg } ) still contains some rst crap we need to handle"
201
+ )
178
202
179
203
s .append (f" — { argdoc } \n \n " )
180
204
else :
@@ -189,30 +213,90 @@ def endpoint_sort_key(rule):
189
213
190
214
more = read_snippet (f'{ ep } .md' , depth = 3 )
191
215
if more :
216
+ s .append ("\n \n " )
192
217
s .append (more )
193
218
194
219
s .append ("\n \n \n " )
195
220
196
221
197
- out = open (args .output , 'w' ) if out_file else sys .stdout
222
+ out = open (args .output , 'w' ) if out_file else sys .stdout if not args . multi_file else None
198
223
199
224
if section_list [- 1 ]:
200
225
# We have some uncategorized entries, so load the .md for it
201
226
other = read_snippet ('uncategorized.md' )
202
- if other :
203
- section_list [- 1 ].insert (0 , other )
204
- else :
227
+ if not other :
205
228
app .logger .warning (
206
229
"Found uncategorized sections, but uncategorized.md not found; inserting stub"
207
230
)
208
- section_list [- 1 ].insert (0 , "# Uncategorized Endpoints\n \n " )
231
+ other = "# Uncategorized Endpoints\n \n "
232
+
233
+ section_list [- 1 ].insert (0 , other )
234
+ section_snips .append (other )
235
+
236
+ else :
237
+ section_list .pop ()
238
+ section_names .pop ()
239
+
240
+ if args .multi_file :
241
+ if not os .path .exists (args .output ):
242
+ os .makedirs (args .output )
243
+ if not args .overwrite and os .path .exists (args .output + '/index.md' ):
244
+ print (f"{ args .output } /index.md already exists; remove it first or use --overwrite/-W" )
245
+ sys .exit (1 )
246
+
247
+ api_readme_f = open (args .output + '/index.md' , 'w' )
248
+
249
+ section_order = range (0 , len (section_list ))
250
+ if args .multi_file :
251
+ # In multi-file mode we take the order for the index file section from the order it is listed in
252
+ # sidebar.md:
253
+ sidebar = read_snippet ('sidebar.md' )
254
+ shutil .copy (snippets + '/sidebar.md' , args .output + '/sidebar.md' )
255
+
256
+ def pos (i ):
257
+ x = sidebar .find (section_names [i ])
258
+ return x if x >= 0 else len (sidebar )
259
+
260
+ section_order = sorted (section_order , key = pos )
261
+
262
+ for i in section_order :
263
+ if args .multi_file :
264
+ filename = args .output + '/' + section_names [i ] + '.md'
265
+ if not args .overwrite and os .path .exists (filename ):
266
+ print (f"{ filename } already exists; remove it first or use --overwrite/-W to overwrite" )
267
+ sys .exit (1 )
268
+ if out is not None :
269
+ out .close ()
270
+ out = open (filename , 'w' )
271
+
272
+ if section_names [i ] + '.md' not in sidebar :
273
+ app .logger .warning (
274
+ f"{ section_names [i ]} .md not found in snippets/sidebar.md: "
275
+ "section will be missing from the sidebar!"
276
+ )
277
+
278
+ snip = section_snips [i ]
279
+ if snip .startswith (f'{ h1 } ' ):
280
+ preamble = snip .find (f'{ h2 } ' )
281
+ if preamble == - 1 :
282
+ preamble = snip
283
+ else :
284
+ preamble = snip [:preamble ]
285
+ print (
286
+ re .sub (fr'^{ h1 } (.*)' , fr'{ h1 } [\1]({ section_names [i ]} .md)' , preamble ),
287
+ file = api_readme_f ,
288
+ )
289
+ else :
290
+ app .logger .warning (
291
+ f"{ section_names [i ]} section didn't start with expected '# Title', "
292
+ f"cannot embed section link in { args .output } /index.md"
293
+ )
209
294
210
- for s in section_list :
211
- for x in s :
295
+ for x in section_list [i ]:
212
296
print (x , end = '' , file = out )
213
297
print ("\n \n " , file = out )
214
298
215
- if out_file :
299
+ if out is not None and out != sys . stdout :
216
300
out .close ()
217
301
218
302
app .logger .info ("API doc created successfully!" )
0 commit comments