comparison yaml/yaml2xml.py @ 2028:5c55b7054b58

Updated docs for the upcoming NGINX Plus release.
author Ruslan Ermilov <ru@nginx.com>
date Sat, 26 Aug 2017 00:56:05 +0300
parents
children ae16f480c867
comparison
equal deleted inserted replaced
2027:dabca59da4ce 2028:5c55b7054b58
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('\<code\>', r'<literal>', text)
26 t = re.sub('\</code\>', r'</literal>', t)
27
28 t = re.sub('\<i\>', r'<value>', t)
29 t = re.sub('\</i\>', r'</value>', t)
30
31 t = re.sub('\<a href=\"(.*)\"\>(.*)\</a\>', r'<link url="\1">\2</link>', t)
32
33 # [desc](url)
34 t = re.sub('\[(.*)\]\((.*)\)', r'<link url="\2">\1</link>', t)
35
36 # ** foo ** is value
37 t = re.sub('[*?][*?](\w+)[*?][*?]', r'<value>\1</value>', t)
38
39 # * foo * is literal
40 t = re.sub('[*?](\w+)[*?]', r'<literal>\1</literal>', t)
41
42
43 return t.encode('utf-8').rstrip()
44
45
46 def pretty_endpoint(ep):
47 return ep.replace('/',' ').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('(.)([A-Z][a-z]+)', r'\1_\2', name)
77 return re.sub('([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%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))