From ad6e00134398c3dcc65e1fce7bd5b5c4b463562f Mon Sep 17 00:00:00 2001 From: Jacobus Geluk Date: Mon, 7 Mar 2022 10:15:51 +0000 Subject: [PATCH] #13 Added Config object for maturity model parser and many small improvements to that parser --- ekglib/maturity_model_parser/File.py | 40 ++++- ekglib/maturity_model_parser/__init__.py | 7 +- ekglib/maturity_model_parser/__main__.py | 46 ++---- ekglib/maturity_model_parser/capability.py | 42 +++-- .../maturity_model_parser/capability_area.py | 50 ++++-- ekglib/maturity_model_parser/config.py | 31 ++++ ekglib/maturity_model_parser/graph.py | 40 ++++- ekglib/maturity_model_parser/loader.py | 18 +-- .../markdown_document.py | 8 +- .../markdown_generator.py | 12 +- ekglib/maturity_model_parser/model.py | 149 +++++++++++++++++- ekglib/maturity_model_parser/pillar.py | 46 +++--- .../resources/ontologies/maturity-model.ttl | 11 ++ .../bp_strategy_actuation/capability002.ttl | 9 +- .../bp_strategy_actuation/capability003.ttl | 9 +- .../bp_strategy_actuation/capability004.ttl | 9 +- .../test_data/maturity-model/docs/.gitignore | 1 + .../pillar/business/background-and-intro.md | 1 + .../capability/goals/background-and-intro.md | 1 + .../capability/goals/dimensions.md | 2 + .../capability/goals/levels.md | 2 + .../tactics/background-and-intro.md | 1 + .../capability/tactics/dimensions.md | 2 + .../capability/tactics/levels.md | 2 + .../capability/vision/background-and-intro.md | 1 + .../capability/vision/dimensions.md | 2 + .../capability/vision/levels.md | 2 + .../pillar/data/background-and-intro.md | 1 + .../organization/background-and-intro.md | 1 + .../pillar/technology/background-and-intro.md | 1 + .../template/background-and-intro.md | 1 + .../maturity-model/template/dimensions.md | 2 + .../maturity-model/template/levels.md | 2 + tests/test_maturity_model_parser.py | 34 +++- 34 files changed, 439 insertions(+), 147 deletions(-) create mode 100644 ekglib/maturity_model_parser/config.py create mode 100644 tests/test_data/maturity-model/docs/.gitignore create mode 100644 tests/test_data/maturity-model/pillar/business/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/dimensions.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/levels.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/dimensions.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/levels.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/dimensions.md create mode 100644 tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/levels.md create mode 100644 tests/test_data/maturity-model/pillar/data/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/organization/background-and-intro.md create mode 100644 tests/test_data/maturity-model/pillar/technology/background-and-intro.md create mode 100644 tests/test_data/maturity-model/template/background-and-intro.md create mode 100644 tests/test_data/maturity-model/template/dimensions.md create mode 100644 tests/test_data/maturity-model/template/levels.md diff --git a/ekglib/maturity_model_parser/File.py b/ekglib/maturity_model_parser/File.py index 644fd01..56c80fd 100644 --- a/ekglib/maturity_model_parser/File.py +++ b/ekglib/maturity_model_parser/File.py @@ -1,10 +1,10 @@ import os from pathlib import Path -import mkdocs_gen_files - from ekglib import log_item from ekglib.log.various import value_error +from ekglib.maturity_model_parser.config import Config +from ekglib.maturity_model_parser.markdown_document import MarkdownDocument class File(object): @@ -60,10 +60,11 @@ def append_after_second_line(self, data): self.file.write('\n' + file_data[len(first_line + second_line):]) @classmethod - def copy(cls, mkdocs: bool, from_path: Path, to_path: Path): - log_item("Copying", f"{from_path} -> {to_path}") - old_file = File.existing_file(mkdocs=mkdocs, path=from_path) - new_file = File(mkdocs=mkdocs, path=to_path) + def copy(cls, config: Config, from_path: Path, to_path: Path): + if config.verbose: + log_item("Copying", f"{from_path} -> {to_path}") + old_file = File.existing_file(mkdocs=config.mkdocs, path=from_path) + new_file = File(mkdocs=config.mkdocs, path=to_path) new_file.rewrite_all_file(old_file.read_all_content()) @@ -73,3 +74,30 @@ def makedirs(path: Path, hint: str): os.makedirs(path) except FileExistsError: return + + +def copy_template_fragment(from_path: Path, config: Config): + log_item("Fragment not found", from_path) + fragment_base = from_path.name + template_path = config.fragments_root / 'template' / fragment_base + if not template_path.exists(): + raise value_error(f"Template not found: {template_path}") + makedirs(from_path.parent, "Fragments") + File.copy(config=config, from_path=template_path, to_path=from_path) + + +def copy_fragment(md_file: MarkdownDocument, from_path: Path, config: Config): + if not from_path.exists(): + copy_template_fragment(from_path=from_path, config=config) + fragment_base = from_path.name + log_item("Copying fragment", fragment_base) + to_path2 = md_file.path.parent / fragment_base + if config.verbose: + log_item("to", to_path2) + File.copy(config=config, from_path=from_path, to_path=to_path2) + md_file.write( + "\n\n{% include-markdown \"" + f"{fragment_base}" + "\" heading-offset=1 %}", + wrap_width=0 + ) diff --git a/ekglib/maturity_model_parser/__init__.py b/ekglib/maturity_model_parser/__init__.py index 49029f0..196422d 100644 --- a/ekglib/maturity_model_parser/__init__.py +++ b/ekglib/maturity_model_parser/__init__.py @@ -6,14 +6,17 @@ from .loader import MaturityModelLoader # noqa: F401 from .exporter import GraphExporter # noqa: F401 from .markdown_generator import MaturityModelMarkdownGenerator # noqa: F401 -from .__main__ import main, mkdocs_gen_files, mkdocs_gen_files2 # noqa: F401 +from .config import Config # noqa: F401 +from .__main__ import main, run_with_config, run_with_args # noqa: F401 __all__ = [ + 'Config', 'MaturityModelLoader', 'MaturityModelMarkdownGenerator', 'GraphExporter', - 'mkdocs_gen_files', 'main', + 'run_with_config', + 'run_with_args', 'MaturityModelGraph', 'MaturityModelPillar', 'MaturityModelCapabilityArea', diff --git a/ekglib/maturity_model_parser/__main__.py b/ekglib/maturity_model_parser/__main__.py index 2bd74ee..cff602a 100644 --- a/ekglib/maturity_model_parser/__main__.py +++ b/ekglib/maturity_model_parser/__main__.py @@ -1,54 +1,32 @@ import argparse from pathlib import Path +from ekglib.maturity_model_parser.config import Config from ekglib.maturity_model_parser.loader import MaturityModelLoader from ekglib.maturity_model_parser.markdown_generator import MaturityModelMarkdownGenerator -def mkdocs_gen_files(model_root: Path, output_root: Path, docs_root: Path, fragments_root: Path): - loader = MaturityModelLoader( - verbose=True, - model_root=model_root, - docs_root=docs_root, - fragments_root=fragments_root - ) - graph = loader.load() - generator = MaturityModelMarkdownGenerator(graph, model_name="EKG/MM", mkdocs=True, output_root=output_root) - generator.generate() - # exporter = GraphExporter(graph) - # return exporter.export(stream) - return 0 - - -def mkdocs_gen_files2(model_root: Path, output_root: Path, docs_root: Path, fragments_root: Path): - loader = MaturityModelLoader( - verbose=True, - model_root=model_root, - docs_root=docs_root, - fragments_root=fragments_root - ) +def run_with_config(config: Config) -> int: + loader = MaturityModelLoader(config) graph = loader.load() - generator = MaturityModelMarkdownGenerator(graph, model_name="EKG/MM", mkdocs=False, output_root=output_root) + generator = MaturityModelMarkdownGenerator(graph, config) generator.generate() # exporter = GraphExporter(graph) # return exporter.export(stream) return 0 -def runit(args) -> int: - loader = MaturityModelLoader( +def run_with_args(args) -> int: + config = Config( + model_name=args.model, verbose=args.verbose, + mkdocs=False, model_root=Path(args.model_root), docs_root=Path(args.docs_root), - fragments_root=Path(args.fragments_root) + fragments_root=Path(args.fragments_root), + output_root=Path(args.output) ) - graph = loader.load() - generator = MaturityModelMarkdownGenerator(graph, model_name=args.model, mkdocs=False, - output_root=Path(args.output)) - generator.generate() - # exporter = GraphExporter(graph) - # return exporter.export(stream) - return 0 + return run_with_config(config) def main(): @@ -68,7 +46,7 @@ def main(): parser.add_argument('--output', help='The output directory', required=True) parser.add_argument('--model', help='The name of the model', default="EKG/MM") - return runit(parser.parse_args()) + return run_with_args(parser.parse_args()) if __name__ == "__main__": diff --git a/ekglib/maturity_model_parser/capability.py b/ekglib/maturity_model_parser/capability.py index 8865989..63f62bc 100644 --- a/ekglib/maturity_model_parser/capability.py +++ b/ekglib/maturity_model_parser/capability.py @@ -4,7 +4,8 @@ from rdflib import RDFS, DCTERMS from rdflib.term import Node -from .File import makedirs, File +from .File import makedirs, File, copy_fragment +from .config import Config from .markdown_document import MarkdownDocument from .pages_yaml import PagesYaml from ..namespace import MATURIY_MODEL @@ -16,17 +17,25 @@ class MaturityModelCapability: class_label_plural: str = "Capabilities" class_iri = MATURIY_MODEL.Capability - def __init__(self, area: MaturityModelCapabilityArea, capability_node: Node, mkdocs: bool): + def __init__( + self, + area: MaturityModelCapabilityArea, + capability_node: Node, + capability_area_fragments_dir: Path, + config: Config + ): self.graph = area.graph self.area = area - self.capability_node = capability_node - self.mkdocs = mkdocs + self.node = capability_node + self.config = config - self.name = self.graph.name_for(self.capability_node, self.class_label) - self.number = self.graph.capability_number_for(self.capability_node, self.class_label) - self.local_name = self.graph.local_name_for(self.capability_node, self.class_label) - self.local_type_name = self.graph.local_type_name_for(self.capability_node, self.class_label) + self.name = self.graph.name_for(self.node, self.class_label) + self.number = self.graph.capability_number_for(self.node, self.class_label) + self.local_name = self.graph.local_name_for(self.node, self.class_label) + self.local_type_name = self.graph.local_type_name_for(self.node, self.class_label) + self.tag_line = self.graph.tag_line_for(self.node) self.full_dir = self.area.full_dir / self.local_type_name / self.local_name + self.fragments_dir = capability_area_fragments_dir / self.local_type_name / self.local_name self.md_file = None makedirs(self.full_dir, self.class_label) @@ -36,6 +45,7 @@ def generate_markdown(self): "title": f"{self.number}. {self.name}" }) self.generate_summary() + self.copy_fragments() self.md_file.create_md_file() def generate_link_from_area_to_capability(self): @@ -44,9 +54,10 @@ def generate_link_from_area_to_capability(self): link=str(link), text=self.name )) - def summaries(self): - for rdfs_comment in self.graph.g.objects(self.capability_node, DCTERMS.description): - yield str(rdfs_comment).strip() + def copy_fragments(self): + copy_fragment(self.md_file, self.fragments_dir / 'background-and-intro.md', self.config) + copy_fragment(self.md_file, self.fragments_dir / 'dimensions.md', self.config) + copy_fragment(self.md_file, self.fragments_dir / 'levels.md', self.config) def generate_summary(self): # self.md_file.heading(2, "Summary") @@ -57,8 +68,8 @@ def generate_summary(self): wrap_width=0 ) self.md_file.write("\n") - for summary in self.summaries(): - self.md_file.write(summary, wrap_width=0) + self.graph.write_tag_line(self.md_file, self.node, self.class_label) + self.graph.write_description(self.md_file, self.node, self.class_label) @classmethod def generate_index_md(cls, area: MaturityModelCapabilityArea): @@ -74,9 +85,8 @@ def generate_index_md(cls, area: MaturityModelCapabilityArea): ) for capability in area.capabilities(): md_file.heading(2, f"{capability.number}. [{capability.name}](./{capability.local_name}/)") - for summary in capability.summaries(): - md_file.write(str(summary).strip(), wrap_width=0) - md_file.write("\n\n") + graph.write_tag_line(md_file, capability, MaturityModelCapability.class_label) + graph.write_description(md_file, capability, MaturityModelCapability.class_label) md_file.create_md_file() diff --git a/ekglib/maturity_model_parser/capability_area.py b/ekglib/maturity_model_parser/capability_area.py index 07303e4..f982943 100644 --- a/ekglib/maturity_model_parser/capability_area.py +++ b/ekglib/maturity_model_parser/capability_area.py @@ -1,10 +1,11 @@ import textwrap from pathlib import Path -from rdflib import DCTERMS +from rdflib import DCTERMS, RDFS from rdflib.term import Node from .File import makedirs, File +from .config import Config from .markdown_document import MarkdownDocument from .pages_yaml import PagesYaml from .pillar import MaturityModelPillar @@ -17,19 +18,27 @@ class MaturityModelCapabilityArea: class_label_plural: str = "Capability Areas" class_iri = MATURIY_MODEL.CapabilityArea - def __init__(self, pillar: MaturityModelPillar, area_node: Node, mkdocs: bool): + def __init__( + self, + pillar: MaturityModelPillar, + area_node: Node, + pillar_fragments_dir: Path, + config: Config + ): self.md_file = None self.graph = pillar.graph self.pillar = pillar - self.area_node = area_node - self.mkdocs = mkdocs + self.node = area_node + self.config = config self._capabilities = list() - self.name = self.graph.name_for(self.area_node, self.class_label) - self.local_name = self.graph.local_name_for(self.area_node, self.class_label) - self.local_type_name = self.graph.local_type_name_for(self.area_node, self.class_label) + self.name = self.graph.name_for(self.node, self.class_label) + self.local_name = self.graph.local_name_for(self.node, self.class_label) + self.local_type_name = self.graph.local_type_name_for(self.node, self.class_label) + self.tag_line = self.graph.tag_line_for(self.node) self.full_dir = self.pillar.full_dir / self.local_type_name / self.local_name self.full_path = self.full_dir / 'index.md' + self.fragments_dir = pillar_fragments_dir / self.local_type_name / self.local_name makedirs(self.full_dir, self.class_label) def generate_markdown(self): @@ -38,20 +47,22 @@ def generate_markdown(self): self.md_file = MarkdownDocument(path=self.full_path, metadata={ 'title': self.name }) - self.summary() - self.md_file.heading(2, MaturityModelCapability.class_label_plural) + self.generate_summary() self.generate_capabilities() self.md_file.create_md_file() - def summary(self): + def generate_summary(self): # self.md_file.heading(2, "Summary") - self.md_file.new_paragraph( + self.md_file.write( f"The capability area _{self.name}_ is " - f"in the [_{self.pillar.name}_](../../index.md).\n" + f"in the [_{self.pillar.name}_](../../index.md).\n", + wrap_width=0 ) - self.md_file.write("\n") - for rdfs_comment in self.graph.g.objects(self.area_node, DCTERMS.description): - self.md_file.write(str(rdfs_comment).strip(), wrap_width=0) + self.generate_summary_short(self.md_file) + + def generate_summary_short(self, md_file: MarkdownDocument): + self.graph.write_tag_line(md_file, self.node, self.class_label) + self.graph.write_description(md_file, self.node, self.class_label) def generate_link_from_pillar_to_capability_area(self): link = Path('.') / self.local_type_name / self.local_name / 'index.md' @@ -60,7 +71,7 @@ def generate_link_from_pillar_to_capability_area(self): )) def capabiliy_nodes_unsorted(self): - for capability_node in self.graph.g.subjects(MATURIY_MODEL.inArea, self.area_node): + for capability_node in self.graph.g.subjects(MATURIY_MODEL.inArea, self.node): yield capability_node def sort_key(self, element): @@ -79,7 +90,7 @@ def capability_nodes(self): def capabilities_non_cached(self): from .capability import MaturityModelCapability for capability_node in self.capability_nodes(): - yield MaturityModelCapability(self, capability_node, self.mkdocs) + yield MaturityModelCapability(self, capability_node, self.fragments_dir, self.config) def capabilities(self): if len(self._capabilities) == 0: @@ -88,6 +99,7 @@ def capabilities(self): def generate_capabilities(self): from .capability import MaturityModelCapability + self.md_file.heading(2, MaturityModelCapability.class_label_plural) MaturityModelCapability.generate_index_md(self) MaturityModelCapability.generate_pages_yaml(self) for capability in self.capabilities(): @@ -102,6 +114,10 @@ def generate_index_md(cls, pillar: MaturityModelPillar): md_file = MarkdownDocument(path=root / 'index.md', metadata={ "title": f"{pillar.name} --- {cls.class_label_plural}" }) + for area in pillar.capability_areas(): + md_file.heading(2, area.name, area.local_name) + area.generate_summary_short(md_file) + md_file.write(f'\n\n[More info...]({area.local_name})\n\n') md_file.create_md_file() @classmethod diff --git a/ekglib/maturity_model_parser/config.py b/ekglib/maturity_model_parser/config.py new file mode 100644 index 0000000..ece5313 --- /dev/null +++ b/ekglib/maturity_model_parser/config.py @@ -0,0 +1,31 @@ +from pathlib import Path + +from ekglib.log.various import value_error + + +class Config: + + def __init__( + self, + model_name: str, + verbose: bool, + mkdocs: bool, + model_root: Path, + docs_root: Path, + output_root: Path, + fragments_root: Path + ): + self.model_name = model_name + self.verbose = verbose + self.mkdocs = mkdocs + self.model_root = model_root + self.docs_root = docs_root + self.output_root = output_root + self.fragments_root = fragments_root + + if not self.model_root.is_dir(): + raise value_error("{} is not a valid directory", self.model_root.name) + if not self.docs_root.is_dir(): + raise value_error("{} is not a valid directory", self.docs_root.name) + if not self.fragments_root.is_dir(): + raise value_error("{} is not a valid directory", self.fragments_root.name) diff --git a/ekglib/maturity_model_parser/graph.py b/ekglib/maturity_model_parser/graph.py index e5c60c0..007b13b 100644 --- a/ekglib/maturity_model_parser/graph.py +++ b/ekglib/maturity_model_parser/graph.py @@ -1,15 +1,40 @@ +import textwrap from pathlib import Path from typing import Optional, Iterable import rdflib -from rdflib import Graph, RDF, OWL, URIRef +from rdflib import Graph, RDF, OWL, URIRef, RDFS, DCTERMS from rdflib.term import Node, Literal from ekglib import log_item from ekglib.log.various import value_error, warning +from ekglib.maturity_model_parser.markdown_document import MarkdownDocument from ekglib.namespace import MATURIY_MODEL +def get_text_in_language( + graph: Graph, + lang: str, + subject: Node, + predicate: URIRef +): + default_value = None + for value in graph.objects(subject, predicate): + if not isinstance(value, rdflib.Literal): + raise value_error(f"Found non-literal as value for {predicate} for subject {subject}") + literal: Literal = value + literal_lang = literal.language + if literal_lang == lang: + return textwrap.dedent(str(literal)).strip() + if literal_lang is None: + default_value = literal + if default_value is None and literal_lang == 'en': + default_value = literal + if default_value is None: + return None + return textwrap.dedent(str(default_value)).strip() + + class MaturityModelGraph: g: rdflib.Graph @@ -37,6 +62,9 @@ def name_for(self, subject_uri, hint: str) -> str: return name raise value_error(f"{hint} has no label: {subject_uri}") + def tag_line_for(self, node: Node): + return get_text_in_language(self.g, self.lang, node, RDFS.comment) + def capability_number_for(self, capability_node, hint: str): for number in self.g.objects(capability_node, MATURIY_MODEL.capabilityNumber): log_item(f"{hint} Number", number) @@ -158,5 +186,15 @@ def create_sort_keys(self): sort_key = f'{capability_number_parts[0]}.{capability_number_parts[1]:0>3}.{capability_number_parts[2]:0>3}' self.g.add((subject, MATURIY_MODEL.sortKey, Literal(sort_key))) + def write_tag_line(self, md: MarkdownDocument, node: Node, _hint: str): + tag_line = self.tag_line_for(node) + if tag_line: + md.write(f'\n_{tag_line}_\n', wrap_width=0) + def description_of(self, node: Node, _hint: str): + return get_text_in_language(self.g, self.lang, node, DCTERMS.description) + def write_description(self, md: MarkdownDocument, node: Node, hint: str): + dct_description = self.description_of(node, hint) + if dct_description: + md.write(dct_description, wrap_width=0) diff --git a/ekglib/maturity_model_parser/loader.py b/ekglib/maturity_model_parser/loader.py index e7711b3..25c6530 100644 --- a/ekglib/maturity_model_parser/loader.py +++ b/ekglib/maturity_model_parser/loader.py @@ -10,6 +10,7 @@ from ekglib.log.various import value_error from ekglib.main.main import load_rdf_stream_into_graph from ekglib.maturity_model_parser.graph import MaturityModelGraph +from .config import Config from ..kgiri import EKG_NS from ..log import error, log_item from ..main import load_rdf_file_into_graph @@ -51,19 +52,12 @@ class MaturityModelLoader: model_root: Path g: rdflib.Graph - def __init__(self, verbose: bool, model_root: Path, docs_root: Path, fragments_root: Path): + def __init__(self, config: Config): - self.verbose = verbose - self.model_root = model_root - self.docs_root = docs_root - self.fragments_root = fragments_root + self.config = config self.g = Graph() self.g.base = "https://maturity-model.ekgf.org/" - if not self.model_root.is_dir(): - raise value_error("{} is not a valid directory", self.model_root.name) - if not self.docs_root.is_dir(): - raise value_error("{} is not a valid directory", self.model_root.name) def load(self) -> MaturityModelGraph: self.load_ontologies() @@ -71,10 +65,10 @@ def load(self) -> MaturityModelGraph: self.rdfs_infer() log_item("# triples", len(self.g)) # dump_as_ttl_to_stdout(self.g) - graph = MaturityModelGraph(self.g, self.verbose, 'en') + graph = MaturityModelGraph(self.g, self.config.verbose, 'en') if len(list(graph.models())) == 0: raise value_error("No models loaded") - graph.rewrite_fragment_references(self.fragments_root) + graph.rewrite_fragment_references(self.config.fragments_root) graph.create_sort_keys() return graph @@ -90,7 +84,7 @@ def load_ontologies(self): def load_model_files(self): # for turtle_file in self.root_directory.rglob("*.ttl"): # log_item("Going to load", turtle_file) - for turtle_file in self.model_root.rglob("*.ttl"): + for turtle_file in self.config.model_root.rglob("*.ttl"): self.load_model_file(Path(turtle_file)) def load_model_file(self, turtle_file: Path): diff --git a/ekglib/maturity_model_parser/markdown_document.py b/ekglib/maturity_model_parser/markdown_document.py index d473292..5af4403 100644 --- a/ekglib/maturity_model_parser/markdown_document.py +++ b/ekglib/maturity_model_parser/markdown_document.py @@ -34,6 +34,7 @@ def __init__(self, path: Path, metadata: dict = {}): :type file_name: str """ log_item("Creating", path) + self.path = path self.file_name = str(path) # TODO: change all to Path self.textUtils = TextUtils self.file_data_text = "" @@ -66,9 +67,12 @@ def read_md_file(self, file_name): return file_data - def heading(self, level: int, title: str): + def heading(self, level: int, title: str, link: str = None): hdr = "#" * level - self.write(f"\n{hdr} {title}\n\n") + if link: + self.write(f"\n{hdr} [{title}]({link})\n\n") + else: + self.write(f"\n{hdr} {title}\n\n") def new_table(self, columns, rows, text, text_align='center', marker=''): """This method takes a list of strings and creates a table. diff --git a/ekglib/maturity_model_parser/markdown_generator.py b/ekglib/maturity_model_parser/markdown_generator.py index c646c0e..4574cc8 100644 --- a/ekglib/maturity_model_parser/markdown_generator.py +++ b/ekglib/maturity_model_parser/markdown_generator.py @@ -4,6 +4,7 @@ from rdflib import OWL from .File import makedirs +from .config import Config from .graph import MaturityModelGraph from .model import MaturityModel from .pillar import MaturityModelPillar @@ -24,18 +25,17 @@ class MaturityModelMarkdownGenerator: output_root: Path md_file: Optional[MarkdownDocument] = None - def __init__(self, graph: MaturityModelGraph, model_name: str, mkdocs: bool, output_root: Path): + def __init__(self, graph: MaturityModelGraph, config: Config): self.graph = graph - self.mkdocs = mkdocs - self.output_root = output_root + self.config = config - self.model = MaturityModel(graph=self.graph, model_name=model_name, mkdocs=mkdocs, output_root=output_root) + self.model = MaturityModel(graph=self.graph, config=config) self.local_type_name = self.graph.local_type_name_for_type( MATURIY_MODEL.Pillar, MaturityModelPillar.class_label ) - self.full_dir = self.output_root / self.local_type_name - makedirs(self.full_dir, "Model") + self.full_dir = self.config.output_root / self.local_type_name + makedirs(self.full_dir, MaturityModel.class_label) self.full_path = self.full_dir / 'index.md' def generate(self): diff --git a/ekglib/maturity_model_parser/model.py b/ekglib/maturity_model_parser/model.py index 9eea979..b013cba 100644 --- a/ekglib/maturity_model_parser/model.py +++ b/ekglib/maturity_model_parser/model.py @@ -5,8 +5,10 @@ from mdutils import MdUtils from rdflib.term import Node +from .config import Config from .markdown_document import MarkdownDocument from .pages_yaml import PagesYaml +from .. import log_item from ..namespace import MATURIY_MODEL from .graph import MaturityModelGraph from .File import makedirs, File @@ -17,32 +19,32 @@ class MaturityModel: class_label: str = "Model" class_label_plural: str = "Models" - def __init__(self, graph: MaturityModelGraph, model_name: str, mkdocs: bool, output_root: Path): + def __init__(self, graph: MaturityModelGraph, config: Config): self.md_file = None self.graph = graph - self.model_node = graph.model_with_name(model_name) - self.mkdocs = mkdocs - self.output_root = output_root + self.model_node = graph.model_with_name(config.model_name) + self.config = config self.name = self.graph.name_for(self.model_node, self.class_label) self.local_name = self.graph.local_name_for(self.model_node, self.class_label) self.local_type_name = self.graph.local_type_name_for(self.model_node, self.class_label) - self.full_dir = self.output_root / self.local_type_name + self.full_dir = self.config.output_root / self.local_type_name self.pillar_type_name = self.graph.local_type_name_for_type(MATURIY_MODEL.Pillar, MaturityModelPillar.class_label) - self.pillars_root = self.output_root / self.pillar_type_name + self.pillars_root = self.config.output_root / self.pillar_type_name makedirs(self.full_dir, self.class_label) self.pillars = list(map(lambda pillar_node: MaturityModelPillar( graph=self.graph, model_node=self.model_node, pillar_node=pillar_node, - mkdocs=mkdocs, - output_root=output_root + config=self.config ), self.graph.pillars(self.model_node))) def generate(self): self.generate_pillars_pages_yaml() self.generate_pillars_index() self.generate_pillars() + self.generate_capabilities_overview_table() + # self.generate_capabilities_overview() def generate_pillars_pages_yaml(self): pages_yaml = PagesYaml(root=self.pillars_root, title="Pillars") @@ -62,3 +64,134 @@ def generate_pillars_index(self): def generate_pillars(self): for pillar in self.pillars: pillar.generate() + + def generate_capabilities_overview_table(self): + overview_md_path = self.config.docs_root / 'intro' / 'overview.md' + makedirs(overview_md_path.parent, "Overview") + overview_md = MarkdownDocument(path=overview_md_path, metadata={ + "title": "Overview", + "hide": [ + "navigation", + "toc" + ] + }) + # '\n\n\n' + overview_md.write( + "The taxonomy of pillars, capability areas, capabilities, and their measurable abilities or summaries.\n\n" + '
\n' + '\n\n' + "\n" + "\n" + "\n" + "\n" + "\n" + "\n\n\n", + wrap_width=0 + ) + for pillar_index, pillar in enumerate(self.pillars): + pillar_url = f'/pillar/{pillar.local_name}' + overview_md.write( + '\n' + '\n' + '\n', + wrap_width=0 + ) + areas = pillar.capability_areas() + for area_index, area in enumerate(areas): + capabilities = area.capabilities() + area_url = f'{pillar_url}/capability-area/{area.local_name}' + if area_index == 0: + overview_md.write( + '\n' + f'\n', + wrap_width=0 + ) + else: + overview_md.write( + '\n', + wrap_width=0 + ) + overview_md.write( + f'\n' + '\n', + wrap_width=0 + ) + for capability_index, capability in enumerate(capabilities): + capability_url = f'{area_url}/capability/{capability.local_name}' + tag_line = capability.tag_line + if tag_line is None: + tag_line = "... todo ..." + if capability_index == 0: + overview_md.write( + '\n' + f'\n', + wrap_width=0 + ) + else: + overview_md.write( + '\n', + wrap_width=0 + ) + overview_md.write( + f'\n' + f'\n' + f'\n' + '\n', + wrap_width=0 + ) + overview_md.write( + '\n' + '
PillarArea CapabilityMeasurable ability
' + f'{pillar.name}
' + '
' + f'{area.name}
{capability.number}{capability.name}{tag_line}
\n', + wrap_width=0 + ) + overview_md.create_md_file() + + def generate_capabilities_overview(self): + overview_md_path = self.config.docs_root / 'intro' / 'overview.md' + makedirs(overview_md_path.parent, "Overview") + overview_md = MarkdownDocument(path=overview_md_path, metadata={ + "title": "Overview", + "hide": [ + "navigation", + "toc" + ] + }) + # "|Pillar|Area| |Capability|
Measurable ability
|\n" + overview_md.write( + "The taxonomy of pillars, capability areas, capabilities, and their measurable abilities or summaries.\n\n" + "|Pillar|Area| |Capability|Measurable ability|\n" + "|------|----|------|----------|------------------|\n", + wrap_width=0 + ) + for pillar_index, pillar in enumerate(self.pillars): + pillar_url = f'/pillar/{pillar.local_name}' + overview_md.write( + f"|[{pillar.name}]({pillar_url}) {{ .foo }}| ~~ | ~~ | ~~ | ~~ |\n", + wrap_width=0 + ) + areas = pillar.capability_areas() + for area_index, area in enumerate(areas): + capabilities = area.capabilities() + last_area = area_index == (len(areas) - 1) + pillar_filler = "_ _" if last_area and len(capabilities) == 0 else " " + area_url = f'{pillar_url}/capability-area/{area.local_name}' + overview_md.write( + f"|{pillar_filler}|[{area.name}]({area_url}) {{ .foo }}| ~~ |~~ | ~~ |\n", + wrap_width=0 + ) + for capability_index, capability in enumerate(capabilities): + last_capability = capability_index == (len(capabilities) - 1) + pillar_filler = "_ _" if last_area and last_capability else " " + area_filler = "_ _" if last_capability else " " + capability_url = f'{area_url}/capability/{capability.local_name}' + tag_line = capability.tag_line + if tag_line is None: + tag_line = "... todo ..." + overview_md.write( + f"|{pillar_filler}|{area_filler}|{capability.number}|[{capability.name}]({capability_url})|{tag_line}|\n", + wrap_width=0 + ) + overview_md.create_md_file() \ No newline at end of file diff --git a/ekglib/maturity_model_parser/pillar.py b/ekglib/maturity_model_parser/pillar.py index 24b0178..0096532 100644 --- a/ekglib/maturity_model_parser/pillar.py +++ b/ekglib/maturity_model_parser/pillar.py @@ -1,33 +1,29 @@ -import os -import textwrap -from pathlib import Path -from typing import Optional +from rdflib.term import Node -from mdutils import MdUtils from rdflib.term import Node -from .markdown_document import MarkdownDocument -from ..log import log_item +from .File import makedirs, copy_fragment +from .config import Config from .graph import MaturityModelGraph -from .File import makedirs, File +from .markdown_document import MarkdownDocument class MaturityModelPillar: class_label: str = "Pillar" class_label_plural: str = "Pillars" - def __init__(self, graph: MaturityModelGraph, model_node: Node, pillar_node: Node, mkdocs: bool, output_root: Path): + def __init__(self, graph: MaturityModelGraph, model_node: Node, pillar_node: Node, config: Config): self.md_file = None self.graph = graph self.model_node = model_node self.pillar_node = pillar_node - self.mkdocs = mkdocs - self.output_root = output_root + self.config = config self.name = self.graph.name_for(self.pillar_node, self.class_label) self.local_name = self.graph.local_name_for(self.pillar_node, self.class_label) self.local_type_name = self.graph.local_type_name_for(self.pillar_node, self.class_label) - self.full_dir = self.output_root / self.local_type_name / self.local_name + self.full_dir = self.config.output_root / self.local_type_name / self.local_name + self.fragments_dir = self.config.fragments_root / self.local_type_name / self.local_name self._capability_areas = list() makedirs(self.full_dir, self.class_label) @@ -35,24 +31,12 @@ def generate(self): self.md_file = MarkdownDocument(path=self.full_dir / 'index.md', metadata={ "title": self.name }) - self.md_file.heading(2, "Capability Areas") self.generate_capability_areas() - for fragment in self.graph.fragment_background_and_intro(self.pillar_node): - self.copy_fragment(from_path=Path(str(fragment)), to_path=self.full_dir) - self.md_file.write( - "\n\n{% include-markdown \"" - f"{fragment}" - "\" heading-offset=1 %}", - wrap_width=0 - ) + self.copy_fragments() self.md_file.create_md_file() - def copy_fragment(self, from_path: Path, to_path: Path): - log_item("Copying fragment", from_path) - to_path2 = to_path / from_path.name - log_item("to", to_path2) - log_item("Current directory", os.getcwd()) - File.copy(self.mkdocs, from_path=from_path, to_path=to_path2) + def copy_fragments(self): + copy_fragment(self.md_file, self.fragments_dir / 'background-and-intro.md', self.config) def capability_area_nodes(self): return self.graph.capability_areas_of_pillar(self.pillar_node) @@ -60,7 +44,7 @@ def capability_area_nodes(self): def capability_areas_not_cached(self): from .capability_area import MaturityModelCapabilityArea for area in self.capability_area_nodes(): - yield MaturityModelCapabilityArea(self, area, self.mkdocs) + yield MaturityModelCapabilityArea(self, area, self.fragments_dir, self.config) def capability_areas(self): if len(self._capability_areas) == 0: @@ -69,8 +53,14 @@ def capability_areas(self): def generate_capability_areas(self): from .capability_area import MaturityModelCapabilityArea + self.md_file.heading(2, MaturityModelCapabilityArea.class_label_plural) MaturityModelCapabilityArea.generate_index_md(self) MaturityModelCapabilityArea.generate_pages_yaml(self) for area in self.capability_areas(): area.generate_markdown() + def number_of_capabilities(self) -> int: + count = 0 + for area in self.capability_areas(): + count += len(area.capabilities()) + return count diff --git a/ekglib/resources/ontologies/maturity-model.ttl b/ekglib/resources/ontologies/maturity-model.ttl index a7c87ed..f33daba 100644 --- a/ekglib/resources/ontologies/maturity-model.ttl +++ b/ekglib/resources/ontologies/maturity-model.ttl @@ -97,6 +97,17 @@ rdfs:label "pillarInModel"@en . +################################################################# +# Data properties +################################################################# + +### https://ekgf.github.io/ekglib/ontology/maturity-model/capabilityNumber +:capabilityNumber rdf:type owl:DatatypeProperty , + owl:FunctionalProperty ; + rdfs:range xsd:string ; + rdfs:comment "A capability number in the format `..` where `` is an uppercase letter and `` and `` are integer numbers."@en . + + ################################################################# # Classes ################################################################# diff --git a/tests/test_data/maturity-model/bp_strategy_actuation/capability002.ttl b/tests/test_data/maturity-model/bp_strategy_actuation/capability002.ttl index 199c268..a905816 100644 --- a/tests/test_data/maturity-model/bp_strategy_actuation/capability002.ttl +++ b/tests/test_data/maturity-model/bp_strategy_actuation/capability002.ttl @@ -6,9 +6,10 @@ @prefix ekgmm: . - a ekgmm:Capability ; - rdfs:label "Business Vision" ; - ekgmm:iriLocalName "vision" ; - ekgmm:inArea ; + a ekgmm:Capability ; + rdfs:label "Business Vision" ; + ekgmm:iriLocalName "vision" ; + ekgmm:inArea ; + ekgmm:capabilityNumber "A.1.1" ; . diff --git a/tests/test_data/maturity-model/bp_strategy_actuation/capability003.ttl b/tests/test_data/maturity-model/bp_strategy_actuation/capability003.ttl index c405858..6f2c847 100644 --- a/tests/test_data/maturity-model/bp_strategy_actuation/capability003.ttl +++ b/tests/test_data/maturity-model/bp_strategy_actuation/capability003.ttl @@ -6,8 +6,9 @@ @prefix ekgmm: . - a ekgmm:Capability ; - rdfs:label "Business Goals" ; - ekgmm:iriLocalName "goals" ; - ekgmm:inArea ; + a ekgmm:Capability ; + rdfs:label "Business Goals" ; + ekgmm:iriLocalName "goals" ; + ekgmm:inArea ; + ekgmm:capabilityNumber "A.1.2" ; . diff --git a/tests/test_data/maturity-model/bp_strategy_actuation/capability004.ttl b/tests/test_data/maturity-model/bp_strategy_actuation/capability004.ttl index 7919df7..fa8440d 100644 --- a/tests/test_data/maturity-model/bp_strategy_actuation/capability004.ttl +++ b/tests/test_data/maturity-model/bp_strategy_actuation/capability004.ttl @@ -6,8 +6,9 @@ @prefix ekgmm: . - a ekgmm:Capability ; - rdfs:label "Business Tactics" ; - ekgmm:iriLocalName "tactics" ; - ekgmm:inArea ; + a ekgmm:Capability ; + rdfs:label "Business Tactics" ; + ekgmm:iriLocalName "tactics" ; + ekgmm:inArea ; + ekgmm:capabilityNumber "A.1.3" ; . diff --git a/tests/test_data/maturity-model/docs/.gitignore b/tests/test_data/maturity-model/docs/.gitignore new file mode 100644 index 0000000..25be18b --- /dev/null +++ b/tests/test_data/maturity-model/docs/.gitignore @@ -0,0 +1 @@ +intro/ diff --git a/tests/test_data/maturity-model/pillar/business/background-and-intro.md b/tests/test_data/maturity-model/pillar/business/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/background-and-intro.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/dimensions.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/dimensions.md new file mode 100644 index 0000000..698a68c --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/dimensions.md @@ -0,0 +1,2 @@ +# Dimensions + diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/levels.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/levels.md new file mode 100644 index 0000000..d0ee4f9 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/goals/levels.md @@ -0,0 +1,2 @@ +# Levels + diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/background-and-intro.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/dimensions.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/dimensions.md new file mode 100644 index 0000000..698a68c --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/dimensions.md @@ -0,0 +1,2 @@ +# Dimensions + diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/levels.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/levels.md new file mode 100644 index 0000000..d0ee4f9 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/tactics/levels.md @@ -0,0 +1,2 @@ +# Levels + diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/background-and-intro.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/dimensions.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/dimensions.md new file mode 100644 index 0000000..698a68c --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/dimensions.md @@ -0,0 +1,2 @@ +# Dimensions + diff --git a/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/levels.md b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/levels.md new file mode 100644 index 0000000..d0ee4f9 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/business/capability-area/strategy-actuation/capability/vision/levels.md @@ -0,0 +1,2 @@ +# Levels + diff --git a/tests/test_data/maturity-model/pillar/data/background-and-intro.md b/tests/test_data/maturity-model/pillar/data/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/data/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/organization/background-and-intro.md b/tests/test_data/maturity-model/pillar/organization/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/organization/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/pillar/technology/background-and-intro.md b/tests/test_data/maturity-model/pillar/technology/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/pillar/technology/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/template/background-and-intro.md b/tests/test_data/maturity-model/template/background-and-intro.md new file mode 100644 index 0000000..f5c12b0 --- /dev/null +++ b/tests/test_data/maturity-model/template/background-and-intro.md @@ -0,0 +1 @@ +# Background & Intro diff --git a/tests/test_data/maturity-model/template/dimensions.md b/tests/test_data/maturity-model/template/dimensions.md new file mode 100644 index 0000000..698a68c --- /dev/null +++ b/tests/test_data/maturity-model/template/dimensions.md @@ -0,0 +1,2 @@ +# Dimensions + diff --git a/tests/test_data/maturity-model/template/levels.md b/tests/test_data/maturity-model/template/levels.md new file mode 100644 index 0000000..d0ee4f9 --- /dev/null +++ b/tests/test_data/maturity-model/template/levels.md @@ -0,0 +1,2 @@ +# Levels + diff --git a/tests/test_maturity_model_parser.py b/tests/test_maturity_model_parser.py index 0ac542b..d7469ae 100644 --- a/tests/test_maturity_model_parser.py +++ b/tests/test_maturity_model_parser.py @@ -1,17 +1,43 @@ import sys -import textwrap from pathlib import Path -from rdflib import URIRef +from rdflib import URIRef, Graph, RDFS +from rdflib.term import Literal import ekglib from ekglib import log_item from ekglib.maturity_model_parser import MaturityModelLoader +from ekglib.maturity_model_parser.graph import get_text_in_language from ekglib.maturity_model_parser.pages_yaml import PagesYaml class TestMaturityModelParser: + def test_get_text_in_language1(self): + graph = Graph() + subject = URIRef("https://test") + graph.add((subject, RDFS.comment, Literal("english", lang="en"))) + graph.add((subject, RDFS.comment, Literal("french", lang="fr"))) + graph.add((subject, RDFS.comment, Literal("nolang"))) + english = get_text_in_language(graph, 'en', subject, RDFS.comment) + assert english == "english" + french = get_text_in_language(graph, 'fr', subject, RDFS.comment) + assert french == "french" + dutch = get_text_in_language(graph, 'nl', subject, RDFS.comment) + assert dutch == "nolang" + + def test_get_text_in_language2(self): + graph = Graph() + subject = URIRef("https://test") + graph.add((subject, RDFS.comment, Literal("english", lang="en"))) + graph.add((subject, RDFS.comment, Literal("french", lang="fr"))) + english = get_text_in_language(graph, 'en', subject, RDFS.comment) + assert english == "english" + french = get_text_in_language(graph, 'fr', subject, RDFS.comment) + assert french == "french" + dutch = get_text_in_language(graph, 'nl', subject, RDFS.comment) + assert dutch == "english" + def test_pages_yaml(self, test_output_dir): yaml = PagesYaml(root=Path(test_output_dir), title="TestABC") yaml.add('somepage.md') @@ -63,7 +89,9 @@ def test_maturity_model_parser_003(self, test_data_dir, test_output_dir): sys.argv = [ 'pytest', '--model-root', f"{test_data_dir}/maturity-model", - '--docs-root', f"{test_output_dir}/ekgmm_test_003", + '--docs-root', f"{test_data_dir}/maturity-model/docs", + '--fragments-root', f"{test_data_dir}/maturity-model", + '--output', f"{test_output_dir}/ekgmm_test_003", '--model', "Test EKG/MM", '--verbose' ]