Skip to content

Commit

Permalink
evedoc.py: script to generate rst doc from eve schema
Browse files Browse the repository at this point in the history
Also supports a "--flat" command line option to produce a "dot"
separated version of all the fields in the EVE schema.
  • Loading branch information
jasonish authored and victorjulien committed Oct 7, 2024
1 parent 2b16369 commit 2626895
Showing 1 changed file with 211 additions and 0 deletions.
211 changes: 211 additions & 0 deletions scripts/evedoc.py
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())

0 comments on commit 2626895

Please sign in to comment.