diff 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
line wrap: on
line diff
new file mode 100755
--- /dev/null
+++ b/yaml/yaml2xml.py
@@ -0,0 +1,523 @@
+#!/usr/bin/env python
+
+# Copyright (C) Nginx, Inc.
+
+import sys, re, datetime
+
+from yaml import load, dump
+from collections import OrderedDict
+
+try:
+    from yaml import CLoader as Loader, CDumper as Dumper, resolver as resolver
+
+except ImportError:
+    from yaml import Loader, Dumper, resolver
+
+
+# primitive markdown parser and utf encoding for output
+def node_description(node):
+
+    text = node.get('description')
+    if text == None:
+        return ""
+
+    #
+    t = re.sub('\<code\>', r'<literal>', text)
+    t = re.sub('\</code\>', r'</literal>', t)
+
+    t = re.sub('\<i\>', r'<value>', t)
+    t = re.sub('\</i\>', r'</value>', t)
+
+    t = re.sub('\<a href=\"(.*)\"\>(.*)\</a\>', r'<link url="\1">\2</link>', t)
+
+    # [desc](url)
+    t = re.sub('\[(.*)\]\((.*)\)', r'<link url="\2">\1</link>', t)
+
+    # ** foo ** is value
+    t = re.sub('[*?][*?](\w+)[*?][*?]', r'<value>\1</value>', t)
+
+    # * foo * is literal
+    t = re.sub('[*?](\w+)[*?]', r'<literal>\1</literal>', t)
+
+
+    return t.encode('utf-8').rstrip()
+
+
+def pretty_endpoint(ep):
+    return ep.replace('/',' ').replace('_',' ')
+
+
+# human-readable html element id based on path
+def path_to_id(path):
+    if path == '/':
+        return 'root'
+
+    str = path.replace('/', '_')
+    str = str.replace('{', '')
+    str = str.replace('}','')
+
+    return uncamelcase(str[1:])
+
+
+def multiple(str):
+    fin2 = str[-2:]
+    fin = str[-1:]
+
+    if fin2 == 's' or fin2 == 'sh' or fin2 == 'ch':
+        last = 'es'
+    elif fin == 'x' or fin == 'z':
+        last = 'es'
+    else:
+        last = 's'
+
+    return str + last
+
+def uncamelcase(name):
+    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
+
+def make_defid(str):
+    return 'def_' + uncamelcase(str)
+
+# returns name of a referenced object
+def get_refname(obj):
+    return obj['$ref'][14:] # remove '#/definitions/'
+
+
+# returns referenced object itself from global definitions table
+def node_from_ref(doc, obj):
+    return doc['definitions'][get_refname(obj)]
+
+
+def render_doc(doc):
+
+    out = "<section id=\"endpoints\" name=\"Endpoints\">\n"
+    out += render_paths(doc)
+    out += "</section>\n"
+
+    # dry run, perform refcount
+    render_defs(doc)
+
+    out += "<section id=\"definitions\" name=\"Response Objects\">\n"
+    out += render_defs(doc)
+    out += "</section>\n"
+
+    return out
+
+
+def render_paths(doc):
+
+    global curr_endpoint
+
+    paths = doc['paths']
+
+    out = "<para>\n"
+    out += "<list type=\"tag\">\n"
+
+    for path_key in paths:
+        path_id = path_to_id(path_key)
+        curr_endpoint = path_key
+        out += render_path(doc, path_key, paths[path_key], path_id)
+
+    curr_endpoint = None
+
+    out += "</list>\n"
+    out += "</para>\n"
+
+    return out
+
+
+def render_defs(doc):
+
+    out = "<para>\n"
+    out += "<list type=\"bullet\">\n"
+
+    for d in doc['definitions']:
+
+        if refs.get(d) == None:
+            continue
+
+        node = doc['definitions'][d]
+
+        out += "<listitem id=\"%s\">\n" % make_defid(d)
+        title = node.get('title', '')
+
+        out += "<para>%s:</para>\n" % title
+
+        out += render_node(doc, d, node, True)
+
+        out += "</listitem>\n"
+
+    out += "</list>\n"
+    out += "</para>\n"
+
+    return out
+
+
+def render_path(doc, path_key, path, path_id):
+
+    out = "<tag-name id=\"%s\" name=\"%s\">\n" % (path_id, path_key)
+    out += "<literal>%s</literal>\n" % path_key
+    out += "</tag-name>\n"
+
+    out += "<tag-desc>\n"
+
+    # List of common method parameters
+    for method_key in path:
+        if method_key != 'parameters':
+            continue
+
+        out += "Parameters common for all methods:\n"
+        out += render_parameters(doc, path[method_key])
+
+
+    # List of methods for this path
+    out += '<para>Supported methods:</para>\n'
+    out += '<list type="bullet" compact="yes">\n'
+
+
+    for method_key in ['get', 'post', 'patch', 'delete']:
+
+        if path.get(method_key) == None:
+            continue
+
+        method = path[method_key]
+
+        id = method['operationId']
+        summ = method['summary']
+        desc = node_description(method)
+        name = method_key.upper()
+
+        out += "<listitem id=\"%s\">\n" % id
+        out += "<literal>%s</literal> - %s\n" % (name, summ)
+        out += "<para>%s</para>\n" % desc
+
+        out += render_method(doc, name, method)
+
+        out += "</listitem>\n"
+
+    out += "</list>\n"
+    out += "</tag-desc>\n"
+
+    return out
+
+
+def render_method(doc, method_name, method):
+
+    out = ""
+
+    if method.get('parameters'):
+        out += "<para>\n"
+        out += "Request parameters:\n"
+        out += render_parameters(doc, method['parameters'])
+        out += "</para>\n"
+
+    out += "<para>\n"
+    out += "Possible responses:\n"
+    out += "</para>\n"
+
+    out += "<list type=\"bullet\">\n"
+
+    for response_key in method['responses']:
+        out += "<listitem>"
+        out += render_response(doc, response_key, method['responses'][response_key])
+        out += "</listitem>\n"
+
+    out += "</list>\n"
+
+    return out
+
+
+def render_parameters(doc, params):
+
+    out = '<list type="tag">\n'
+
+    for p in params:
+
+        out += "<tag-name><literal>%s</literal>\n" % p['name']
+        out += "("
+
+        out += render_node(doc, None, p, True)
+
+        if p.get("required"):
+
+            if p["required"] == True:
+                out += ", required"
+            else:
+                out += ", optional"
+        else:
+            out += ", optional"
+
+        out += ")"
+
+        out += "</tag-name>\n"
+        out += "<tag-desc>\n"
+
+        desc = node_description(p)
+        out += desc
+
+        out += "</tag-desc>\n"
+
+    out += "</list>\n"
+
+    return out
+
+
+def render_response(doc, response_key, response):
+
+    out = ""
+
+    desc = node_description(response)
+
+    out += response_key + " - " + desc
+
+    if response.get('schema'):
+        out += ", returns "
+        out += render_node(doc, None, response)
+
+    return out
+
+
+
+def render_reference(doc, nodename, node):
+
+    global in_array, refs
+
+    out = ""
+
+    ref = get_refname(node)
+
+    refnode = node_from_ref(doc, node)
+
+    if refnode.get('additionalProperties'):
+        # in entries
+
+        out += "a collection of "
+        ref = get_refname(refnode['additionalProperties'])
+        target = node_from_ref(doc, refnode['additionalProperties'])
+        label = target.get('title', ref)
+        out += "\"<link id=\"%s\">%s</link>\"" % (make_defid(ref), label)
+        out += " objects"
+        refs[ref] = 1
+        if curr_endpoint != None:
+            out += " for all %s" % pretty_endpoint(curr_endpoint)
+
+        return out
+
+    # arrays and primitive types are printed immediately
+    nt = refnode.get('type', 'object')
+    title = refnode.get('title', ref)
+    if nt == 'object':
+        if in_array == True:
+            title = multiple(title)
+
+        out += "<link id=\"%s\">%s</link>" % (make_defid(ref), title)
+        refs[ref] = 1
+
+    elif nt  == 'array':
+
+        if nodename == 'peers':
+            ref = get_refname(refnode['items'])
+            out += "An array of:"
+            refnode = node_from_ref(doc, refnode['items'])
+            out += render_node(doc, ref, refnode, True)
+            return out
+
+        out += "an array of "
+
+        in_array = True
+        out += render_node(doc, nodename, refnode['items'], True)
+        in_array = False
+    else:
+        # dead code actually
+        out += "<literal>%s</literal>\n" % nt
+
+    return out
+
+
+# displays object recursively if described inline, or generates links
+def render_node(doc, nodename, node, show_type=False):
+
+    if node.get('$ref'):
+        return render_reference(doc, nodename, node)
+
+    elif node.get('schema'):
+        return render_reference(doc, nodename, node['schema'])
+
+    out = ""
+
+    if node.get('additionalProperties'):
+        # in definitions
+        ref = get_refname(node['additionalProperties'])
+        target = node_from_ref(doc, node['additionalProperties'])
+        label = target.get('title', ref)
+        desc = node_description(node)
+        out += "<para>%s</para><para>A collection of " % desc
+        out += "\"<link id=\"%s\">%s</link>\"" % (make_defid(ref), label)
+        refs[ref] = 1
+        out += " objects</para>\n"
+
+        return out
+
+    nt = node.get('type', 'object')
+
+    if nt == 'object':
+        out += render_object(doc, node)
+
+    elif nt == 'array':
+        desc = node_description(node)
+        out += "<para>%s</para>\n" % desc
+        out += "array element type:\n"
+        out += render_node(doc, nodename, node['items'], True)
+
+    else:
+        if show_type:
+            if in_array == True:
+                out += "%s" % multiple(node['type'])
+            else:
+                out += "<literal>%s</literal>" % node['type']
+
+    if node.get('example'):
+        out += render_example(node['example'])
+
+    return out
+
+def json_simple_type(obj):
+
+    if isinstance(obj, bool):
+        if obj == True:
+            return 'true'
+        else:
+            return 'false'
+
+    elif isinstance(obj, str):
+        return '"' + obj + '"'
+
+    elif isinstance(obj,datetime.datetime):
+        t = obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
+        z = obj.strftime("Z%Z")
+        return '"' + t + z + '"'
+
+    else:
+        return str(obj)
+
+def render_example(obj, level = 0):
+
+    out = ""
+
+    if level == 0:
+        if isinstance(obj, dict) or isinstance(obj, list):
+            out += "<para>Example:</para>\n"
+        else:
+            out += "Example: <literal>%s</literal>\n" % json_simple_type(obj)
+            return out
+
+        out += "<example>\n"
+
+    indent = '  ' * level
+    next_indent = '  ' * (level + 1)
+
+    if isinstance(obj, dict):
+        out += '{\n'
+        i = 0
+        last = len(obj)
+        for key in obj:
+            out += next_indent + '"' + str(key) + '" : '
+            out += render_example(obj[key], level + 1)
+            if i != last - 1:
+                out += ','
+            out += '\n'
+            i = i + 1
+        out += indent + "}"
+
+    elif isinstance(obj, list):
+        out += '[\n'
+        i = 0
+        last = len(obj)
+        for item in obj:
+            out += next_indent
+            out += render_example(item, level + 1)
+            if i != last - 1:
+                out += ','
+            out += '\n'
+            i = i + 1
+        out += indent + "]"
+    else:
+        out += json_simple_type(obj)
+
+    if level == 0:
+        out += "</example>\n"
+
+
+    return out
+
+
+def render_object(doc, obj):
+
+    out = ""
+
+    if obj.get('description'):
+        desc = node_description(obj)
+        out += desc
+
+    if obj.get('properties') == None:
+        return out
+
+    out += '<list type="tag">\n'
+    for p in obj['properties']:
+
+        prop = obj['properties'][p]
+
+        out += "<tag-name>\n"
+        out += "<literal>%s</literal>" % p
+
+        if prop.get('properties') or prop.get('type') == 'object':
+            obj_type = None             # there is nested object
+        else:
+            if prop.get('type'):
+                obj_type = prop['type'] # basic type
+            else:
+                obj_type = None         # there is a reference
+
+        if obj_type != None:
+            out += " (<literal>%s</literal>)\n" % obj_type
+
+        out += "</tag-name>\n"
+        out += "<tag-desc>\n"
+
+        if prop.get('description') and obj_type != None:
+            desc = node_description(prop)
+            out += desc + '\n'
+
+        out += render_node(doc, p, prop)
+
+        out += "</tag-desc>\n"
+    out += "</list>\n"
+
+    return out
+
+
+###############################################################################
+
+if len(sys.argv) < 2:
+    print("Usage: %s <nginx_api.yaml>" % sys.argv[0])
+    sys.exit(1)
+
+refs = dict()
+curr_endpoint = None
+in_array = False
+
+def ordered_load(stream, Loader=Loader, object_pairs_hook=OrderedDict):
+    class OrderedLoader(Loader):
+        pass
+    def construct_mapping(loader, node):
+        loader.flatten_mapping(node)
+        return object_pairs_hook(loader.construct_pairs(node))
+    OrderedLoader.add_constructor(
+        resolver.BaseResolver.DEFAULT_MAPPING_TAG,construct_mapping)
+    return load(stream, OrderedLoader)
+
+with open(sys.argv[1], 'r') as src:
+    content = src.read()
+    doc = ordered_load(content, Loader=Loader)
+    print(render_doc(doc))