| import importlib |
| import warnings |
| import re |
|
|
| from docutils.parsers.rst import Directive |
| from docutils import nodes, utils |
| from sphinx import addnodes |
| from recommonmark.parser import CommonMarkParser |
|
|
|
|
| def type_description(schema): |
| """Return a concise type description for the given schema""" |
| if not schema or not isinstance(schema, dict) or schema.keys() == {"description"}: |
| return "any" |
| elif "$ref" in schema: |
| return ":class:`{}`".format(schema["$ref"].split("/")[-1]) |
| elif "enum" in schema: |
| return "[{}]".format(", ".join(repr(s) for s in schema["enum"])) |
| elif "type" in schema: |
| if isinstance(schema["type"], list): |
| return "[{}]".format(", ".join(schema["type"])) |
| elif schema["type"] == "array": |
| return "array({})".format(type_description(schema.get("items", {}))) |
| elif schema["type"] == "object": |
| return "dict" |
| else: |
| return "`{}`".format(schema["type"]) |
| elif "anyOf" in schema: |
| return "anyOf({})".format( |
| ", ".join(type_description(s) for s in schema["anyOf"]) |
| ) |
| else: |
| warnings.warn( |
| "cannot infer type for schema with keys {}" "".format(schema.keys()) |
| ) |
| return "--" |
|
|
|
|
| def prepare_table_header(titles, widths): |
| """Build docutil empty table""" |
| ncols = len(titles) |
| assert len(widths) == ncols |
|
|
| tgroup = nodes.tgroup(cols=ncols) |
| for width in widths: |
| tgroup += nodes.colspec(colwidth=width) |
| header = nodes.row() |
| for title in titles: |
| header += nodes.entry("", nodes.paragraph(text=title)) |
| tgroup += nodes.thead("", header) |
|
|
| tbody = nodes.tbody() |
| tgroup += tbody |
|
|
| return nodes.table("", tgroup), tbody |
|
|
|
|
| reClassDef = re.compile(r":class:`([^`]+)`") |
| reCode = re.compile(r"`([^`]+)`") |
|
|
|
|
| def add_class_def(node, classDef): |
| """Add reference on classDef to node""" |
|
|
| ref = addnodes.pending_xref( |
| reftarget=classDef, |
| reftype="class", |
| refdomain="py", |
| refexplicit=False, |
| |
| refwarn=False, |
| ) |
| ref["py:class"] = "None" |
| ref["py:module"] = "altair" |
|
|
| ref += nodes.literal(text=classDef, classes=["xref", "py", "py-class"]) |
| node += ref |
| return node |
|
|
|
|
| def add_text(node, text): |
| """Add text with inline code to node""" |
| is_text = True |
| for part in reCode.split(text): |
| if part: |
| if is_text: |
| node += nodes.Text(part, part) |
| else: |
| node += nodes.literal(part, part) |
|
|
| is_text = not is_text |
|
|
| return node |
|
|
|
|
| def build_row(item): |
| """Return nodes.row with property description""" |
|
|
| prop, propschema, required = item |
| row = nodes.row() |
|
|
| |
|
|
| row += nodes.entry("", nodes.paragraph(text=prop), classes=["vl-prop"]) |
|
|
| |
| str_type = type_description(propschema) |
| par_type = nodes.paragraph() |
|
|
| is_text = True |
| for part in reClassDef.split(str_type): |
| if part: |
| if is_text: |
| add_text(par_type, part) |
| else: |
| add_class_def(par_type, part) |
| is_text = not is_text |
|
|
| |
| row += nodes.entry("", par_type) |
|
|
| |
| md_parser = CommonMarkParser() |
| |
| str_descr = "" |
| str_descr += propschema.get("description", " ") |
| doc_descr = utils.new_document("schema_description") |
| md_parser.parse(str_descr, doc_descr) |
|
|
| |
| row += nodes.entry("", *doc_descr.children, classes=["vl-decsr"]) |
|
|
| return row |
|
|
|
|
| def build_schema_tabel(items): |
| """Return schema table of items (iterator of prop, schema.item, requred)""" |
| table, tbody = prepare_table_header( |
| ["Property", "Type", "Description"], [10, 20, 50] |
| ) |
| for item in items: |
| tbody += build_row(item) |
|
|
| return table |
|
|
|
|
| def select_items_from_schema(schema, props=None): |
| """Return iterator (prop, schema.item, requred) on prop, return all in None""" |
| properties = schema.get("properties", {}) |
| required = schema.get("required", []) |
| if not props: |
| for prop, item in properties.items(): |
| yield prop, item, prop in required |
| else: |
| for prop in props: |
| try: |
| yield prop, properties[prop], prop in required |
| except KeyError: |
| warnings.warn("Can't find property:", prop) |
|
|
|
|
| def prepare_schema_tabel(schema, props=None): |
|
|
| items = select_items_from_schema(schema, props) |
| return build_schema_tabel(items) |
|
|
|
|
| class AltairObjectTableDirective(Directive): |
| """ |
| Directive for building a table of attribute descriptions. |
| |
| Usage: |
| |
| .. altair-object-table:: altair.MarkConfig |
| |
| """ |
|
|
| has_content = False |
| required_arguments = 1 |
|
|
| def run(self): |
|
|
| objectname = self.arguments[0] |
| modname, classname = objectname.rsplit(".", 1) |
| module = importlib.import_module(modname) |
| cls = getattr(module, classname) |
| schema = cls.resolve_references(cls._schema) |
|
|
| |
| table = prepare_schema_tabel(schema) |
| return [table] |
|
|
|
|
| def setup(app): |
| app.add_directive("altair-object-table", AltairObjectTableDirective) |
|
|