mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-04-22 10:28:02 +00:00
196 lines
5.3 KiB
Python
196 lines
5.3 KiB
Python
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", # py:class="None" py:module="altair" refdoc="user_guide/marks"
|
|
refexplicit=False,
|
|
# refdoc="",
|
|
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()
|
|
|
|
# Property
|
|
|
|
row += nodes.entry("", nodes.paragraph(text=prop), classes=["vl-prop"])
|
|
|
|
# Type
|
|
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('')
|
|
row += nodes.entry("", par_type) # , classes=["vl-type-def"]
|
|
|
|
# Description
|
|
md_parser = CommonMarkParser()
|
|
# str_descr = "***Required.*** " if required else ""
|
|
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")
|
|
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)
|
|
|
|
# create the table from the object
|
|
table = prepare_schema_tabel(schema)
|
|
return [table]
|
|
|
|
|
|
def setup(app):
|
|
app.add_directive("altair-object-table", AltairObjectTableDirective)
|