Mercurial > hg > nginx-site
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)) |