diff --git a/tools/jsonschema2moindoc.py b/tools/jsonschema2moindoc.py new file mode 100755 index 0000000000000000000000000000000000000000..4e20397ee1bbd9666e60bba8772adef0c6e6d5a4 --- /dev/null +++ b/tools/jsonschema2moindoc.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from sys import argv, stderr +from json import load, dumps +from collections import Sequence, OrderedDict +from urllib import unquote +from pprint import pprint +import re + + +def flee(s): + print >>stderr, s + exit(1) + + +def loadJSON(p): + try: + with open(str(p), "r") as f: + try: + json = load(f, object_pairs_hook=OrderedDict) + except ValueError, err: + flee ("%s: %s" % (p, err)) + except IOError, err: + flee(err) + + return json + + +def resolve_ref(document, fragment): + """ + Resolve $ref. Only local relative URIs are supported. + """ + fragment = unquote(fragment).lstrip("#").lstrip("/") + parts = fragment.split("/") if fragment else [] + + for part in parts: + part = part.replace("~1", "/").replace("~0", "~") + + if isinstance(document, Sequence): + # Array indexes should be turned into integers + try: + part = int(part) + except ValueError: + pass + try: + document = document[part] + except (TypeError, LookupError): + flee("Unresolvable JSON pointer: %r" % fragment) + + return document + + +def is_camel_cased(s): + return re.search('[a-z].*[A-Z].*[a-z]', s) is not None + + +def process_simple(sch, name, parent, ordlist, card, whole, indent, res): + """ + Generate Moin format entry + """ + + if "$ref" in sch and sch["$ref"].startswith("#/definitions/"): + mytype = sch["$ref"].split("/")[-1] + mytype = u"[[#" + mytype + u"|" + mytype + u"]]" + else: + mytype = sch["type"] + mytype = u"''" + mytype + u"''" + + res["def"].append( + u"%(indent)s''%(nocamel)s%(name)s'': %(card)s%(type)s\n%(indent)s\t%(desc)s\n" % { + "indent": u"\t" * indent, + "name": name, + "type": mytype, + "card": u"array of " if card else u"", + "desc": sch["description"], + "nocamel": u"!" if is_camel_cased(name) else u"" + } + ) + + +def recurse(sch, name, parent, ordlist, card, whole, indent, res): + """ + Recurse into subtrees based on type. + Also crudely resolves references (by merging into current tree). + """ + # Crudely resolve references (by merging into current tree) + if "$ref" in sch: + ref = resolve_ref(whole, sch["$ref"]) + sch = dict(ref.items() + sch.items()) + + # Jumptable recursion + if "type" in sch: + fork[sch["type"]](sch, name, parent, ordlist, card, whole, indent, res) + else: + flee("Basic type not defined: %s", pprint(schema)) + + +def process_object(sch, name, parent, ordlist, card, whole, indent, res): + """ + Process object type and recurse for children + """ + # Generate data for non root objects only + if name: + process_simple(sch, name, parent, ordlist, card, whole, indent, res) + + # Set ordinality list for direct subobject of this object + neword = sch["required"] if "required" in sch else [] + + # Process each child, with False cardinality + for n, subsch in sch["properties"].iteritems(): + recurse(subsch, n, name, neword, False, whole, indent+1, res) + + +def process_array(sch, name, parent, ordlist, card, whole, indent, res): + """ + Process array type (recurse just for one "items" children, but indicate + cardinality + """ + recurse(sch["items"], name, parent, ordlist, True, whole, indent, res) + + +fork = { + "object": process_object, + "array": process_array, + "string": process_simple, + "number": process_simple, + "integer": process_simple, + "boolean": process_simple +} + + +def process_types(schema, res): + """ + Process Types section + """ + for t, vals in schema["definitions"].iteritems(): + res["types"].append( + u"<<Anchor(%(name)s)>>\n=== %(name)s ===\n\n%(desc)s\n\n" % { + "name": t, + "desc": vals["description"] + } + ) + + +def main(): + if len(argv)==2: + schp = argv[1] + else: + flee("Usage: %s <jsonschemafile>" % split(argv[0])[-1]) + + schema = loadJSON(schp) + + res = { + "def": [], + "types": [] + } + # First structure of JSON schema must be object + process_object(schema, None, "", [""], False, schema, -1, res) + + process_types(schema, res) + + print u"".join( + [schema["description"]] + + [u"\n\n== Definition ==\n\n"] + + res["def"] + + [u"\n<<Anchor(Types)>>\n== Types ==\n\n"] + + res["types"] + ).encode("utf-8") + + +main()