Generates HTML documentation explaining all of configuration fields (#2952)
* create HTML documentation explaining all of schema's configuration fields
This commit is contained in:
parent
e2c1547df6
commit
c228734978
5 changed files with 1320 additions and 0 deletions
43
util/config/configdocs/configdoc.py
Normal file
43
util/config/configdocs/configdoc.py
Normal 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)
|
93
util/config/configdocs/docsmodel.py
Normal file
93
util/config/configdocs/docsmodel.py
Normal 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
|
63
util/config/configdocs/html_output.py
Normal file
63
util/config/configdocs/html_output.py
Normal 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
|
||||
|
1043
util/config/configdocs/schema.html
Normal file
1043
util/config/configdocs/schema.html
Normal file
File diff suppressed because it is too large
Load diff
78
util/config/configdocs/style.css
Normal file
78
util/config/configdocs/style.css
Normal 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;
|
||||
}
|
Reference in a new issue