mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.3 KiB
Python
212 lines
6.3 KiB
Python
#! /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())
|