Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
nginx
GitHub Repository: nginx/nginx.org
Path: blob/main/yaml/yaml2xml.py
1 views
1
#!/usr/bin/env python
2
3
# Copyright (C) Nginx, Inc.
4
5
import sys, re, datetime
6
7
from yaml import load, dump
8
from collections import OrderedDict
9
10
try:
11
from yaml import CLoader as Loader, CDumper as Dumper, resolver as resolver
12
13
except ImportError:
14
from yaml import Loader, Dumper, resolver
15
16
17
# primitive markdown parser and utf encoding for output
18
def node_description(node):
19
20
text = node.get('description')
21
if text == None:
22
return ""
23
24
#
25
t = re.sub(r'\<code\>', r'<literal>', text)
26
t = re.sub(r'\</code\>', r'</literal>', t)
27
28
t = re.sub(r'\<i\>', r'<value>', t)
29
t = re.sub(r'\</i\>', r'</value>', t)
30
31
t = re.sub(r'\<a href=\"(.*?)\"\>(.*?)\</a\>', r'<link url="\1">\2</link>', t)
32
33
# [desc](url)
34
t = re.sub(r'\[(.*)\]\((.*?)\)', r'<link url="\2">\1</link>', t)
35
36
# ** foo ** is value
37
t = re.sub(r'[*?][*?](\w+)[*?][*?]', r'<value>\1</value>', t)
38
39
# * foo * is literal
40
t = re.sub(r'[*?](\w+)[*?]', r'<literal>\1</literal>', t)
41
42
43
return t.encode('utf-8').rstrip().decode()
44
45
46
def pretty_endpoint(ep):
47
return ep.replace('/slabs/','slabs').replace('/resolvers/','resolvers').replace('/http/','HTTP ').replace('/stream/','stream ').replace('/workers/','workers').replace('s/','s').replace('_',' ')
48
49
50
# human-readable html element id based on path
51
def path_to_id(path):
52
if path == '/':
53
return 'root'
54
55
str = path.replace('/', '_')
56
str = str.replace('{', '')
57
str = str.replace('}','')
58
59
return uncamelcase(str[1:])
60
61
62
def multiple(str):
63
fin2 = str[-2:]
64
fin = str[-1:]
65
66
if fin2 == 's' or fin2 == 'sh' or fin2 == 'ch':
67
last = 'es'
68
elif fin == 'x' or fin == 'z':
69
last = 'es'
70
else:
71
last = 's'
72
73
return str + last
74
75
def uncamelcase(name):
76
s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
77
return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
78
79
def make_defid(str):
80
return 'def_' + uncamelcase(str)
81
82
# returns name of a referenced object
83
def get_refname(obj):
84
return obj['$ref'][14:] # remove '#/definitions/'
85
86
87
# returns referenced object itself from global definitions table
88
def node_from_ref(doc, obj):
89
return doc['definitions'][get_refname(obj)]
90
91
92
def render_doc(doc):
93
94
out = "<section id=\"endpoints\" name=\"Endpoints\">\n"
95
out += render_paths(doc)
96
out += "</section>\n"
97
98
# dry run, perform refcount
99
render_defs(doc)
100
101
out += "<section id=\"definitions\" name=\"Response Objects\">\n"
102
out += render_defs(doc)
103
out += "</section>\n"
104
105
return out
106
107
108
def render_paths(doc):
109
110
global curr_endpoint
111
112
paths = doc['paths']
113
114
out = "<para>\n"
115
out += "<list type=\"tag\">\n"
116
117
for path_key in paths:
118
path_id = path_to_id(path_key)
119
curr_endpoint = path_key
120
out += render_path(doc, path_key, paths[path_key], path_id)
121
122
curr_endpoint = None
123
124
out += "</list>\n"
125
out += "</para>\n"
126
127
return out
128
129
130
def render_defs(doc):
131
132
out = "<para>\n"
133
out += "<list type=\"bullet\">\n"
134
135
for d in doc['definitions']:
136
137
if refs.get(d) == None:
138
continue
139
140
node = doc['definitions'][d]
141
142
out += "<listitem id=\"%s\">\n" % make_defid(d)
143
title = node.get('title', '')
144
145
out += "<para>%s:</para>\n" % title
146
147
out += render_node(doc, d, node, True)
148
149
out += "</listitem>\n"
150
151
out += "</list>\n"
152
out += "</para>\n"
153
154
return out
155
156
157
def render_path(doc, path_key, path, path_id):
158
159
out = "<tag-name id=\"%s\" name=\"%s\">\n" % (path_id, path_key)
160
out += "<literal>%s</literal>\n" % path_key
161
out += "</tag-name>\n"
162
163
out += "<tag-desc>\n"
164
165
# List of common method parameters
166
for method_key in path:
167
if method_key != 'parameters':
168
continue
169
170
out += "Parameters common for all methods:\n"
171
out += render_parameters(doc, path[method_key])
172
173
174
# List of methods for this path
175
out += '<para>Supported methods:</para>\n'
176
out += '<list type="bullet" compact="yes">\n'
177
178
179
for method_key in ['get', 'post', 'patch', 'delete']:
180
181
if path.get(method_key) == None:
182
continue
183
184
method = path[method_key]
185
186
id = method['operationId']
187
summ = method['summary']
188
desc = node_description(method)
189
name = method_key.upper()
190
191
out += "<listitem id=\"%s\">\n" % id
192
out += "<literal>%s</literal> - %s\n" % (name, summ)
193
out += "<para>%s</para>\n" % desc
194
195
out += render_method(doc, name, method)
196
197
out += "</listitem>\n"
198
199
out += "</list>\n"
200
out += "</tag-desc>\n"
201
202
return out
203
204
205
def render_method(doc, method_name, method):
206
207
out = ""
208
209
if method.get('parameters'):
210
out += "<para>\n"
211
out += "Request parameters:\n"
212
out += render_parameters(doc, method['parameters'])
213
out += "</para>\n"
214
215
out += "<para>\n"
216
out += "Possible responses:\n"
217
out += "</para>\n"
218
219
out += "<list type=\"bullet\">\n"
220
221
for response_key in method['responses']:
222
out += "<listitem>"
223
out += render_response(doc, response_key, method['responses'][response_key])
224
out += "</listitem>\n"
225
226
out += "</list>\n"
227
228
return out
229
230
231
def render_parameters(doc, params):
232
233
out = '<list type="tag">\n'
234
235
for p in params:
236
237
out += "<tag-name><literal>%s</literal>\n" % p['name']
238
out += "("
239
240
out += render_node(doc, None, p, True)
241
242
if p.get("required"):
243
244
if p["required"] == True:
245
out += ", required"
246
else:
247
out += ", optional"
248
else:
249
out += ", optional"
250
251
out += ")"
252
253
out += "</tag-name>\n"
254
out += "<tag-desc>\n"
255
256
desc = node_description(p)
257
out += desc
258
259
out += "</tag-desc>\n"
260
261
out += "</list>\n"
262
263
return out
264
265
266
def render_response(doc, response_key, response):
267
268
out = ""
269
270
desc = node_description(response)
271
272
out += response_key + " - " + desc
273
274
if response.get('schema'):
275
out += ", returns "
276
out += render_node(doc, None, response)
277
278
return out
279
280
281
282
def render_reference(doc, nodename, node):
283
284
global in_array, refs
285
286
out = ""
287
288
ref = get_refname(node)
289
290
refnode = node_from_ref(doc, node)
291
292
if refnode.get('additionalProperties'):
293
# in entries
294
295
out += "a collection of "
296
ref = get_refname(refnode['additionalProperties'])
297
target = node_from_ref(doc, refnode['additionalProperties'])
298
label = target.get('title', ref)
299
out += "\"<link id=\"%s\">%s</link>\"" % (make_defid(ref), label)
300
out += " objects"
301
refs[ref] = 1
302
if curr_endpoint != None:
303
out += " for all %s" % pretty_endpoint(curr_endpoint)
304
305
return out
306
307
# arrays and primitive types are printed immediately
308
nt = refnode.get('type', 'object')
309
title = refnode.get('title', ref)
310
if nt == 'object':
311
if in_array == True:
312
title = multiple(title)
313
314
out += "<link id=\"%s\">%s</link>" % (make_defid(ref), title)
315
refs[ref] = 1
316
317
elif nt == 'array':
318
319
if nodename == 'peers':
320
ref = get_refname(refnode['items'])
321
out += "An array of:"
322
refnode = node_from_ref(doc, refnode['items'])
323
out += render_node(doc, ref, refnode, True)
324
return out
325
326
out += "an array of "
327
328
in_array = True
329
out += render_node(doc, nodename, refnode['items'], True)
330
in_array = False
331
else:
332
# dead code actually
333
out += "<literal>%s</literal>\n" % nt
334
335
return out
336
337
338
# displays object recursively if described inline, or generates links
339
def render_node(doc, nodename, node, show_type=False):
340
341
if node.get('$ref'):
342
return render_reference(doc, nodename, node)
343
344
elif node.get('schema'):
345
return render_reference(doc, nodename, node['schema'])
346
347
out = ""
348
349
if node.get('additionalProperties'):
350
# in definitions
351
ref = get_refname(node['additionalProperties'])
352
target = node_from_ref(doc, node['additionalProperties'])
353
label = target.get('title', ref)
354
desc = node_description(node)
355
out += "<para>%s</para><para>A collection of " % desc
356
out += "\"<link id=\"%s\">%s</link>\"" % (make_defid(ref), label)
357
refs[ref] = 1
358
out += " objects</para>\n"
359
360
return out
361
362
nt = node.get('type', 'object')
363
364
if nt == 'object':
365
out += render_object(doc, node)
366
367
elif nt == 'array':
368
desc = node_description(node)
369
out += "<para>%s</para>\n" % desc
370
out += "array element type:\n"
371
out += render_node(doc, nodename, node['items'], True)
372
373
else:
374
if show_type:
375
if in_array == True:
376
out += "%s" % multiple(node['type'])
377
else:
378
out += "<literal>%s</literal>" % node['type']
379
380
if node.get('example'):
381
out += render_example(node['example'])
382
383
return out
384
385
def json_simple_type(obj):
386
387
if isinstance(obj, bool):
388
if obj == True:
389
return 'true'
390
else:
391
return 'false'
392
393
elif isinstance(obj, str):
394
return '"' + obj + '"'
395
396
elif isinstance(obj,datetime.datetime):
397
t = obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
398
z = obj.strftime("Z")
399
return '"' + t + z + '"'
400
401
else:
402
return str(obj)
403
404
def render_example(obj, level = 0):
405
406
out = ""
407
408
if level == 0:
409
if isinstance(obj, dict) or isinstance(obj, list):
410
out += "<para>Example:</para>\n"
411
else:
412
out += "Example: <literal>%s</literal>\n" % json_simple_type(obj)
413
return out
414
415
out += "<example>\n"
416
417
indent = ' ' * level
418
next_indent = ' ' * (level + 1)
419
420
if isinstance(obj, dict):
421
out += '{\n'
422
i = 0
423
last = len(obj)
424
for key in obj:
425
out += next_indent + '"' + str(key) + '" : '
426
out += render_example(obj[key], level + 1)
427
if i != last - 1:
428
out += ','
429
out += '\n'
430
i = i + 1
431
out += indent + "}"
432
433
elif isinstance(obj, list):
434
out += '[\n'
435
i = 0
436
last = len(obj)
437
for item in obj:
438
out += next_indent
439
out += render_example(item, level + 1)
440
if i != last - 1:
441
out += ','
442
out += '\n'
443
i = i + 1
444
out += indent + "]"
445
else:
446
out += json_simple_type(obj)
447
448
if level == 0:
449
out += "</example>\n"
450
451
452
return out
453
454
455
def render_object(doc, obj):
456
457
out = ""
458
459
if obj.get('description'):
460
desc = node_description(obj)
461
out += desc
462
463
if obj.get('properties') == None:
464
return out
465
466
out += '<list type="tag">\n'
467
for p in obj['properties']:
468
469
prop = obj['properties'][p]
470
471
out += "<tag-name>\n"
472
out += "<literal>%s</literal>" % p
473
474
if prop.get('properties') or prop.get('type') == 'object':
475
obj_type = None # there is nested object
476
else:
477
if prop.get('type'):
478
obj_type = prop['type'] # basic type
479
else:
480
obj_type = None # there is a reference
481
482
if obj_type != None:
483
out += " (<literal>%s</literal>)\n" % obj_type
484
485
out += "</tag-name>\n"
486
out += "<tag-desc>\n"
487
488
if prop.get('description') and obj_type != None:
489
desc = node_description(prop)
490
out += desc + '\n'
491
492
out += render_node(doc, p, prop)
493
494
out += "</tag-desc>\n"
495
out += "</list>\n"
496
497
return out
498
499
500
###############################################################################
501
502
if len(sys.argv) < 2:
503
print("Usage: %s <nginx_api.yaml>" % sys.argv[0])
504
sys.exit(1)
505
506
refs = dict()
507
curr_endpoint = None
508
in_array = False
509
510
def ordered_load(stream, Loader=Loader, object_pairs_hook=OrderedDict):
511
class OrderedLoader(Loader):
512
pass
513
def construct_mapping(loader, node):
514
loader.flatten_mapping(node)
515
return object_pairs_hook(loader.construct_pairs(node))
516
OrderedLoader.add_constructor(
517
resolver.BaseResolver.DEFAULT_MAPPING_TAG,construct_mapping)
518
return load(stream, OrderedLoader)
519
520
with open(sys.argv[1], 'r') as src:
521
content = src.read()
522
doc = ordered_load(content, Loader=Loader)
523
print(render_doc(doc))
524
525