Generates HTML documentation explaining all of configuration fields (#2952)

* create HTML documentation explaining all of schema's configuration fields
This commit is contained in:
IvanCherepov 2018-01-24 14:09:29 -05:00 committed by GitHub
parent e2c1547df6
commit c228734978
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1320 additions and 0 deletions

View file

@ -0,0 +1,43 @@
""" Generates html documentation from JSON Schema """
import json
from collections import OrderedDict
import docsmodel
import html_output
from util.config.schema import CONFIG_SCHEMA
def make_custom_sort(orders):
""" Sort in a specified order any dictionary nested in a complex structure """
orders = [{k: -i for (i, k) in enumerate(reversed(order), 1)} for order in orders]
def process(stuff):
if isinstance(stuff, dict):
l = [(k, process(v)) for (k, v) in stuff.iteritems()]
keys = set(stuff)
for order in orders:
if keys.issubset(order) or keys.issuperset(order):
return OrderedDict(sorted(l, key=lambda x: order.get(x[0], 0)))
return OrderedDict(sorted(l))
if isinstance(stuff, list):
return [process(x) for x in stuff]
return stuff
return process
SCHEMA_HTML_FILE = "schema.html"
schema = json.dumps(CONFIG_SCHEMA, sort_keys = True)
schema = json.loads(schema, object_pairs_hook = OrderedDict)
req = sorted(schema["required"])
custom_sort = make_custom_sort([req])
schema = custom_sort(schema)
parsed_items = docsmodel.DocsModel().parse(schema)[1:]
output = html_output.HtmlOutput().generate_output(parsed_items)
with open(SCHEMA_HTML_FILE, 'wt') as f:
f.write(output)

View file

@ -0,0 +1,93 @@
import json
import collections
class ParsedItem(dict):
""" Parsed Schema item """
def __init__(self, json_object, name, required, level):
"""Fills dict with basic item information"""
super(ParsedItem, self).__init__()
self['name'] = name
self['title'] = json_object.get('title', '')
self['type'] = json_object.get('type')
self['description'] = json_object.get('description', '')
self['level'] = level
self['required'] = required
self['x-reference'] = json_object.get('x-reference', '')
self['x-example'] = json_object.get('x-example', '')
self['pattern'] = json_object.get('pattern', '')
self['enum'] = json_object.get('enum', '')
class DocsModel:
""" Documentation model and Schema Parser """
def __init__(self):
self.__parsed_items = None
def parse(self, json_object):
""" Returns multi-level list of recursively parsed items """
self.__parsed_items = list()
self.__parse_schema(json_object, 'root', True, 0)
return self.__parsed_items
def __parse_schema(self, schema, name, required, level):
""" Parses schema, which type is object, array or leaf.
Appends new ParsedItem to self.__parsed_items lis """
parsed_item = ParsedItem(schema, name, required, level)
self.__parsed_items.append(parsed_item)
required = schema.get('required', [])
if 'enum' in schema:
parsed_item['item'] = schema.get('enum')
item_type = schema.get('type')
if item_type == 'object' and name != 'DISTRIBUTED_STORAGE_CONFIG':
self.__parse_object(parsed_item, schema, required, level)
elif item_type == 'array':
self.__parse_array(parsed_item, schema, required, level)
else:
parse_leaf(parsed_item, schema)
def __parse_object(self, parsed_item, schema, required, level):
""" Parses schema of type object """
for key, value in schema.get('properties', {}).items():
self.__parse_schema(value, key, key in required, level + 1)
def __parse_array(self, parsed_item, schema, required, level):
""" Parses schema of type array """
items = schema.get('items')
parsed_item['minItems'] = schema.get('minItems', None)
parsed_item['maxItems'] = schema.get('maxItems', None)
parsed_item['uniqueItems'] = schema.get('uniqueItems', False)
if isinstance(items, dict):
# item is single schema describing all elements in an array
self.__parse_schema(
items,
'array item',
required,
level + 1)
elif isinstance(items, list):
# item is a list of schemas
for index, list_item in enumerate(items):
self.__parse_schema(
list_item,
'array item {}'.format(index),
index in required,
level + 1)
def parse_leaf(parsed_item, schema):
""" Parses schema of a number and a string """
if parsed_item['name'] != 'root':
parsed_item['description'] = schema.get('description','')
parsed_item['x-reference'] = schema.get('x-reference','')
parsed_item['pattern'] = schema.get('pattern','')
parsed_item['enum'] = ", ".join(schema.get('enum','')).encode()
ex = schema.get('x-example', '')
if isinstance(ex, list):
parsed_item['x-example'] = ", ".join(ex).encode()
elif isinstance(ex, collections.OrderedDict):
parsed_item['x-example'] = json.dumps(ex)
else:
parsed_item['x-example'] = ex

View file

@ -0,0 +1,63 @@
class HtmlOutput:
""" Generates HTML from documentation model """
def __init__(self):
pass
def generate_output(self, parsed_items):
"""Returns generated HTML strin"""
return self.__get_html_begin() + \
self.__get_html_middle(parsed_items) + \
self.__get_html_end()
def __get_html_begin(self):
return '<!DOCTYPE html>\n<html>\n<head>\n<link rel="stylesheet" type="text/css" href="style.css" />\n</head>\n<body>\n'
def __get_html_end(self):
return '</body>\n</html>'
def __get_html_middle(self, parsed_items):
output = ''
root_item = parsed_items[0]
#output += '<h1 class="root_title">{}</h1>\n'.format(root_item['title'])
#output += '<h1 class="root_title">{}</h1>\n'.format(root_item['title'])
output += "Schema for Quay Enterprise"
output += '<ul class="level0">\n'
last_level = 0
is_root = True
for item in parsed_items:
level = item['level'] - 1
if last_level < level:
output += '<ul class="level{}">\n'.format(level)
for i in range(last_level - level):
output += '</ul>\n'
last_level = level
output += self.__get_html_item(item, is_root)
is_root = False
output += '</ul>\n'
return output
def __get_required_field(self, parsed_item):
return 'required' if parsed_item['required'] else ''
def __get_html_item(self, parsed_item, is_root):
item = '<li class="schema item"> \n'
item += '<div class="name">{}</div> \n'.format(parsed_item['name'])
item += '<div class="type">[{}]</div> \n'.format(parsed_item['type'])
item += '<div class="required">{}</div> \n'.format(self.__get_required_field(parsed_item))
item += '<div class="docs">\n' if not is_root else '<div class="root_docs">\n'
item += '<div class="title">{}</div>\n'.format(parsed_item['title'])
item += ': ' if parsed_item['title'] != '' and parsed_item['description'] != '' else ''
item += '<div class="description">{}</div>\n'.format(parsed_item['description'])
item += '<div class="enum">enum: {}</div>\n'.format(parsed_item['enum']) if parsed_item['enum'] != '' else ''
item += '<div class="minItems">Min Items: {}</div>\n'.format(parsed_item['minItems']) if parsed_item['type'] == "array" and parsed_item['minItems'] != "None" else ''
item += '<div class="uniqueItems">Unique Items: {}</div>\n'.format(parsed_item['uniqueItems']) if parsed_item['type'] == "array" and parsed_item['uniqueItems'] else ''
item += '<div class="pattern">Pattern: {}</div>\n'.format(parsed_item['pattern']) if parsed_item['pattern'] != 'None' and parsed_item['pattern'] != '' else ''
item += '<div class="x-reference"><a href="{}">Reference: {}</a></div>\n'.format(parsed_item['x-reference'],parsed_item['x-reference']) if parsed_item['x-reference'] != '' else ''
item += '<div class="x-example">Example: <code>{}</code></div>\n'.format(parsed_item['x-example']) if parsed_item['x-example'] != '' else ''
item += '</div>\n'
item += '</li>\n'
return item

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
body {
font-family: sans-serif;
}
pre, code{
white-space: normal;
}
div.root docs{
display: none;
}
div.name {
display: inline;
}
div.type {
display: inline;
font-weight: bold;
color: blue;
}
div.required {
display: inline;
font-weight: bold;
}
div.docs {
display: block;
}
div.title {
display: block;
font-weight: bold;
}
div.description {
display: block;
font-family: serif;
font-style: italic;
}
div.enum {
display: block;
font-family: serif;
font-style: italic;
}
div.x-example {
display: block;
font-family: serif;
font-style: italic;
margin-bottom: 10px;
}
div.pattern {
display: block;
font-family: serif;
font-style: italic;
}
div.x-reference {
display: block;
font-family: serif;
font-style: italic;
}
div.uniqueItems {
display: block;
}
div.minItems {
display: block;
}
div.maxItems {
display: block;
}