forked from OISF/suricata
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
evedoc.py: script to generate rst doc from eve schema
Also supports a "--flat" command line option to produce a "dot" separated version of all the fields in the EVE schema.
- Loading branch information
1 parent
2b16369
commit 2626895
Showing
1 changed file
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
#! /usr/bin/env python3 | ||
# | ||
# Generate Sphinx documentation from JSON schema | ||
|
||
import argparse | ||
import sys | ||
import json | ||
|
||
|
||
def errprint(*args, **kwargs): | ||
print(*args, file=sys.stderr, **kwargs) | ||
|
||
|
||
def find_ref(schema: dict, ref: str) -> dict: | ||
parts = ref.split("/") | ||
|
||
root = parts.pop(0) | ||
if root != "#": | ||
raise Exception("Unsupported reference: {}".format(ref)) | ||
|
||
while parts: | ||
schema = schema[parts.pop(0)] | ||
|
||
return schema | ||
|
||
|
||
def get_type(props: dict, name: str) -> str: | ||
prop_type = props["type"] | ||
if prop_type == "array": | ||
try: | ||
array_type = props["items"]["type"] | ||
except KeyError: | ||
errprint("warning: array property without items: {}".format(name)) | ||
array_type = "unknown" | ||
prop_type = "array of {}s".format(array_type) | ||
return prop_type | ||
|
||
|
||
def render_flat(schema: dict): | ||
stack = [(schema, [])] | ||
|
||
while stack: | ||
(current, path) = stack.pop(0) | ||
|
||
for name, props in current["properties"].items(): | ||
if "$ref" in props: | ||
ref = find_ref(schema, props["$ref"]) | ||
if not ref: | ||
raise Exception("$ref not found: {}".format(props["$ref"])) | ||
props = ref | ||
if props["type"] in ["string", "integer", "boolean", "number"]: | ||
# End of the line... | ||
print("{}: {}".format(".".join(path + [name]), props["type"])) | ||
elif props["type"] == "object": | ||
print("{}: object".format(".".join(path + [name]))) | ||
if "properties" in props: | ||
stack.insert(0, (props, path + [name])) | ||
else: | ||
errprint( | ||
"warning: object without properties: {}".format( | ||
".".join(path + [name]) | ||
) | ||
) | ||
elif props["type"] == "array": | ||
if "items" in props and "type" in props["items"]: | ||
print( | ||
"{}: {}[]".format( | ||
".".join(path + [name]), props["items"]["type"] | ||
) | ||
) | ||
if "properties" in props["items"]: | ||
stack.insert( | ||
0, | ||
( | ||
props["items"], | ||
path + ["{}[]".format(name)], | ||
), | ||
) | ||
else: | ||
errprint( | ||
"warning: undocumented array: {}".format( | ||
".".join(path + [name]) | ||
) | ||
) | ||
print("{}: array".format(".".join(path + [name]))) | ||
else: | ||
raise Exception("Unsupported type: {}".format(props["type"])) | ||
|
||
|
||
def render_rst(schema: dict): | ||
stack = [(schema, [], "object")] | ||
|
||
while stack: | ||
(current, path, type) = stack.pop(0) | ||
|
||
items = [] | ||
|
||
for name, props in current["properties"].items(): | ||
if "$ref" in props: | ||
ref = find_ref(schema, props["$ref"]) | ||
if not ref: | ||
raise Exception( | ||
"Reference not found: {}".format(props["$ref"]) | ||
) | ||
props = ref | ||
prop_type = get_type(props, name) | ||
description = props.get("description", "") | ||
|
||
items.append( | ||
{"name": name, "type": prop_type, "description": description} | ||
) | ||
|
||
if props["type"] == "object" and "properties" in props: | ||
stack.insert(0, (props, path + [name], "object")) | ||
elif ( | ||
props["type"] == "array" | ||
and "items" in props | ||
and "properties" in props["items"] | ||
): | ||
array_type = props["items"]["type"] | ||
stack.insert( | ||
0, | ||
( | ||
props["items"], | ||
path + ["{}".format(name)], | ||
"array of {}s".format(array_type), | ||
), | ||
) | ||
|
||
render_rst_table(items, path, type) | ||
|
||
|
||
def render_rst_table(items: list, path: list, type: str): | ||
if not path: | ||
title = "Top Level" | ||
else: | ||
title = ".".join(path) | ||
title = "{} ({})".format(title, type) | ||
print(title) | ||
print("^" * len(title)) | ||
|
||
name_len = max([len(item["name"]) for item in items] + [len("Name")]) | ||
desc_len = max( | ||
[len(item["description"]) for item in items] + [len("Description")] | ||
) | ||
type_len = max([len(item["type"]) for item in items]) | ||
|
||
print(".. table::") | ||
print(" :width: 100%") | ||
print(" :widths: 30 25 45") | ||
print("") | ||
|
||
print(" {} {} {}".format("=" * name_len, "=" * type_len, "=" * desc_len)) | ||
print( | ||
" {} {} {}".format( | ||
"Name".ljust(name_len), | ||
"Type".ljust(type_len), | ||
"Description".ljust(desc_len), | ||
) | ||
) | ||
print(" {} {} {}".format("=" * name_len, "=" * type_len, "=" * desc_len)) | ||
for item in items: | ||
print( | ||
" {} {} {}".format( | ||
item["name"].ljust(name_len), | ||
item["type"].ljust(type_len), | ||
item["description"].ljust(desc_len), | ||
) | ||
) | ||
print(" {} {} {}".format("=" * name_len, "=" * type_len, "=" * desc_len)) | ||
print("") | ||
|
||
|
||
epilog = """ | ||
By default, the EVE schema is rendered as Sphinx documentation. To | ||
create "flat" or "dot" separated output, use the --flat option. | ||
""" | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
description="Generate documentation from JSON schema", | ||
epilog=epilog, | ||
formatter_class=argparse.RawTextHelpFormatter, | ||
) | ||
parser.add_argument("--object", help="Object name") | ||
parser.add_argument("--output", help="Output file") | ||
parser.add_argument("--flat", help="Flatten output", action="store_true") | ||
parser.add_argument("filename", help="JSON schema file") | ||
|
||
args = parser.parse_args() | ||
|
||
root = json.load(open(args.filename)) | ||
schema = root | ||
|
||
if args.object: | ||
schema = schema["properties"][args.object] | ||
|
||
if args.output: | ||
sys.stdout = open(args.output, "w") | ||
|
||
if args.flat: | ||
render_flat(schema) | ||
else: | ||
render_rst(schema) | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) |