diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..82e38f3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pip3 install --user -r requirements.txt", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/requirements.txt b/requirements.txt index ce6b84d..fea80c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,7 @@ -# -# These requirements were autogenerated by pipenv -# To regenerate from the project's Pipfile, run: -# -# pipenv lock --requirements -# - --i https://pypi.org/simple isodate==0.6.0 -pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' +Jinja2==3.1.2 +MarkupSafe==2.1.3 +pyparsing==2.4.7 rdflib==6.0.0 -six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +six==1.16.0 sly==0.4 diff --git a/spec_parser/templates/default/class.md.j2 b/spec_parser/templates/default/class.md.j2 new file mode 100644 index 0000000..0ed0693 --- /dev/null +++ b/spec_parser/templates/default/class.md.j2 @@ -0,0 +1,48 @@ + + + + +# {{name}} + +## Summary +{{summary}} + +## Description +{{description}} + +## Metadata +{% for name, vals in metadata | dictsort %} + - {{name}}: {{vals | list | join(" ")}} +{% endfor %} + +## Properties +{% if args.get("use_table", False) %} + header_list = ["type", "minCount", "maxCount"] + + # print the header + f.write("|" + "|".join(["property"] + header_list) + "|\n") + f.write("|" + "---|" * (len(header_list) + 1) + "\n") + + {% for name, subprops in properties | dictsort %} + |{{name}} + {% for subprop in header_list %} + |{{subprops.get(subprop, ["NA"]) | join(" ")}} + {% endfor %} + | + {% endfor %} +{% else %} + {% for name, subprops in properties | dictsort %} + - {{name}} + {% for _key, subprop in subprops | dictsort %} + - {{_key}}: {{subprop | list | join(" ")}} + {% endfor %} + {% endfor %} +{% endif %} + +{% if format_pattern %} + ## Format + + {% for name, vals in format_pattern | dictsort %} + - {{name}}: {{vals | list | join(" ")}} + {% endfor %} +{% endif %} diff --git a/spec_parser/templates/default/property.md.j2 b/spec_parser/templates/default/property.md.j2 new file mode 100644 index 0000000..cf53498 --- /dev/null +++ b/spec_parser/templates/default/property.md.j2 @@ -0,0 +1,23 @@ + + + + +# {{name}} + +## Summary +{{summary}} + +## Description +{{description}} + +## Metadata +{% for name, vals in metadata | dictsort %} + - {{name}}: {{vals | list | join(" ")}} +{% endfor %} + +{% if args.get("gen_refs", False) %} + ## References + {% for name in spec.dataprop_refs.get(self.name, []) | dictsort %} + - {{name}} + {% endfor %} +{% endif %} diff --git a/spec_parser/templates/default/vocab.md.j2 b/spec_parser/templates/default/vocab.md.j2 new file mode 100644 index 0000000..b0fa00c --- /dev/null +++ b/spec_parser/templates/default/vocab.md.j2 @@ -0,0 +1,21 @@ + + + + +# {{name}} + +## Summary +{{summary}} + +## Description +{{description}} + +## Metadata +{% for name, vals in metadata | dictsort %} + - {{name}}: {{vals | list | join(" ")}} +{% endfor %} + +## Entries +{% for name, val in entries | dictsort %} + - {{name}}: {{val}} +{% endfor %} \ No newline at end of file diff --git a/spec_parser/utils.py b/spec_parser/utils.py index 2e5628d..cf2756d 100644 --- a/spec_parser/utils.py +++ b/spec_parser/utils.py @@ -4,6 +4,8 @@ from os import path from typing import List +from jinja2 import Environment, PackageLoader, select_autoescape + import rdflib from rdflib import URIRef, Literal, BNode, SH from rdflib.extras.infixowl import EnumeratedClass @@ -115,6 +117,11 @@ def gen_md(self) -> None: if path.isdir(self.args["out_dir"]): self.logger.warning(f"Overwriting out_dir `{self.args['out_dir']}`") + env = Environment( + loader=PackageLoader("spec_parser", package_path="templates/default"), + autoescape=False + ) + for namespace in self.namespaces.values(): classes = namespace["classes"] @@ -122,13 +129,13 @@ def gen_md(self) -> None: vocabs = namespace["vocabs"] for class_obj in classes.values(): - class_obj._gen_md(self.args) + class_obj._gen_md(env, self.args) for prop_obj in properties.values(): - prop_obj._gen_md(self.args) + prop_obj._gen_md(env, self.args) for vocab_obj in vocabs.values(): - vocab_obj._gen_md(self.args) + vocab_obj._gen_md(env, self.args) def _get_defined_class_types(self) -> List[URIRef]: class_types = [] @@ -386,78 +393,17 @@ def _extract_format(self, format_list): self.format_pattern[_key] = _values - def _gen_md(self, args: dict) -> None: + def _gen_md(self, env, args: dict) -> None: fname = path.join( args["out_dir"], self.namespace_name, "Classes", f"{self.name}.md" ) - with safe_open(fname, "w") as f: - - # write the header - f.write( - f"\n\n" - ) - - # write the license name - f.write( - f"\n\n" - ) + template = env.get_template("class.md.j2") + result = template.render({'__version__': __version__, 'args': args} | vars(self)) - # write the topheadline - f.write(f"# {self.name}\n\n") - - # write the summary - f.write("## Summary\n\n") - f.write(f"{self.summary}\n") - f.write("\n") - - # write the description - f.write("## Description\n\n") - f.write(f"{self.description}\n") - f.write("\n") - - # write the metadata - f.write("## Metadata\n\n") - for name, vals in sorted(self.metadata.items()): - if isinstance(vals, list): - f.write(f'- {name}: {" ".join(vals)}\n') - else: - f.write(f'- {name}: {vals}\n') - f.write("\n") - - # write the data_props - f.write("## Properties\n\n") - if args.get("use_table", False): - # generate markdown-table from properties - header_list = ["type", "minCount", "maxCount"] - - # print the header - f.write("|" + "|".join(["property"] + header_list) + "|\n") - f.write("|" + "---|" * (len(header_list) + 1) + "\n") - - for name, subprops in sorted(self.properties.items()): - f.write(f"|{name}") - for subprop in header_list: - f.write(f'|{" ".join(subprops.get(subprop, ["NA"]))}') - f.write("|\n") - else: - # generate markdown-list from properties - for name, subprops in sorted(self.properties.items()): - f.write(f"- {name}\n") - for _key, subprop in sorted(subprops.items()): - if isinstance(subprop, list): - f.write(f' - {_key}: {" ".join(subprop)}\n') - else: - f.write(f' - {_key}: {subprop}\n') - f.write("\n") - if self.format_pattern: - f.write("## Format\n\n") - for name, vals in sorted(self.format_pattern.items()): - if isinstance(vals, list): - f.write(f'- {name}: {" ".join(vals)}\n') - else: - f.write(f'- {name}: {vals}\n') + with safe_open(fname, "w") as f: + f.write(result) def _gen_rdf(self, g: rdflib.Graph, class_types: List[URIRef]) -> None: @@ -540,51 +486,17 @@ def __init__( self.logger = logging.getLogger(self.__class__.__name__) self._extract_metadata(metadata) - def _gen_md(self, args: dict) -> None: + def _gen_md(self, env, args: dict) -> None: fname = path.join( args["out_dir"], self.namespace_name, "Properties", f"{self.name}.md" ) - with safe_open(fname, "w") as f: + template = env.get_template("property.md.j2") + result = template.render({'__version__': __version__, 'args': args} | vars(self)) - # write the header - f.write( - f"\n\n" - ) - - # write the license name - f.write( - f"\n\n" - ) - - # write the topheadline - f.write(f"# {self.name}\n\n") - - # write the summary - f.write("## Summary\n\n") - f.write(f"{self.summary}\n") - f.write("\n") - - # write the description - f.write("## Description\n\n") - f.write(f"{self.description}\n") - f.write("\n") - - # write the metadata - f.write("## Metadata\n\n") - for name, vals in sorted(self.metadata.items()): - if isinstance(vals, list): - f.write(f'- {name}: {" ".join(vals)}\n') - else: - f.write(f'- {name}: {vals}\n') - f.write("\n") - - if args.get("gen_refs", False): - # Class references - f.write("## References\n\n") - for name in sorted(self.spec.dataprop_refs.get(self.name, [])): - f.write(f"- {name}\n") + with safe_open(fname, "w") as f: + f.write(result) def _gen_rdf(self, g: rdflib.Graph) -> None: @@ -662,51 +574,17 @@ def __init__( self._extract_metadata(metadata) self._extract_entries(entries) - def _gen_md(self, args: dict) -> None: + def _gen_md(self, env, args: dict) -> None: fname = path.join( args["out_dir"], self.namespace_name, "Vocabularies", f"{self.name}.md" ) - with safe_open(fname, "w") as f: - - # write the header - f.write( - f"\n\n" - ) - - # write the license name - f.write( - f"\n\n" - ) - - # write the topheadline - f.write(f"# {self.name}\n\n") - - # write the summary - f.write("## Summary\n\n") - f.write(f"{self.summary}\n") - f.write("\n") - - # write the description - f.write("## Description\n\n") - f.write(f"{self.description}\n") - f.write("\n") - - # write the metadata - f.write("## Metadata\n\n") - for name, vals in sorted(self.metadata.items()): - if isinstance(vals, list): - f.write(f'- {name}: {" ".join(vals)}\n') - else: - f.write(f'- {name}: {vals}\n') - f.write("\n") - - # write the entries - f.write("## Entries\n\n") - for name, val in sorted(self.entries.items()): - f.write(f"- {name}: {val}\n") + template = env.get_template("vocab.md.j2") + result = template.render({'__version__': __version__, 'args': args} | vars(self)) + with safe_open(fname, "w") as f: + f.write(result) def _gen_rdf(self, g: rdflib.Graph):