diff --git a/pretty_print.py b/pretty_print.py
index 494d976..934860a 100644
--- a/pretty_print.py
+++ b/pretty_print.py
@@ -11,12 +11,37 @@ def html_constructor(loader, node):
yaml.add_constructor("!html", html_constructor, Loader=yaml.FullLoader)
-def generate_diff(previous, current):
+def diff_text(previous, current):
dmp = diff_match_patch()
diff = dmp.diff_main("\n".join(previous), "\n".join(current))
dmp.diff_cleanupSemantic(diff)
return dmp.diff_prettyHtml(diff)
+def diff_objects(previous, current):
+ """return html where previous display_name is shon in red and strike-through,
+ followed by arrow sign and current display_name in green"""
+ return f"{previous['display_name']} → {current['display_name']}"
+
+
+def diff_lists(previous, current):
+ out = []
+ previous = {item["uuid"]: item for item in previous}
+ for item in current:
+ if item["uuid"] not in previous:
+ out.append(f"
{item}")
+ elif item["uuid"] in previous:
+ if item["display_name"] != previous[item["uuid"]]["display_name"]:
+ out.append(f"{diff_objects(previous[item['uuid']], item)}")
+ else:
+ out.append(f"{item['display_name']}")
+ current = {item["uuid"]: item for item in current}
+ for item in previous:
+ if item not in current:
+ out.append(f"{previous[item]['display_name']}")
+ return ""
+
+
+
def traverse_and_diff(data):
"""For every key "name" or "description" if it has
child keys "previous" and "current" we perform HtmlDiff
@@ -24,15 +49,22 @@ def traverse_and_diff(data):
"""
updates = {}
for key, value in data.items():
- if key in ["name", "description"] \
- and isinstance(value, dict) \
- and "previous" in value \
- and "current" in value:
- diff = generate_diff(
- value["previous"].splitlines(),
- value["current"].splitlines()
- )
- updates[key] = {"diff": diff}
+ if isinstance(value, dict) and "previous" in value and "current" in value:
+ prev_type = type(value["previous"])
+ curr_type = type(value["current"])
+ if prev_type == curr_type == str:
+ diff = diff_text(
+ value["previous"].splitlines(),
+ value["current"].splitlines()
+ )
+ updates[key] = {"diff": diff}
+ elif prev_type == curr_type == dict:
+ diff = diff_objects(value["previous"], value["current"])
+ updates[key] = {"diff": diff}
+ elif prev_type == curr_type == list:
+ diff = diff_lists(value["previous"], value["current"])
+ updates[key] = {"diff": diff}
+
elif isinstance(value, list):
for item in value:
traverse_and_diff(item)
@@ -42,6 +74,35 @@ def traverse_and_diff(data):
data[key].update(value)
return data
+def compute_diff_stats(data):
+ """We need to compute the quantity of created, modified and deleted items and
+ sum those up for each category and subcategory, like so:
+
+ 1. when the child branch has lists of deletions, creations or modifications we return stats with lengths of those lists as values
+ 2. we aggregate (sum) those stats for each category and subcategory
+ 3. we add stats key + value to each category and subcategory
+ """
+ stats = {}
+ if isinstance(data, dict):
+ if "created" in data:
+ stats["created"] = len(data["created"])
+ if "modified" in data:
+ stats["modified"] = len(data["modified"])
+ if "deleted" in data:
+ stats["deleted"] = len(data["deleted"])
+ if not stats:
+ for key, value in data.items():
+ if isinstance(value, dict):
+ child_stats = compute_diff_stats(value)
+ if "created" in child_stats:
+ stats["created"] = stats.get("created", 0) + child_stats["created"]
+ if "modified" in child_stats:
+ stats["modified"] = stats.get("modified", 0) + child_stats["modified"]
+ if "deleted" in child_stats:
+ stats["deleted"] = stats.get("deleted", 0) + child_stats["deleted"]
+ data["stats"] = stats
+ return stats
+
@click.command()
@click.argument("filename")
@@ -50,6 +111,7 @@ def main(filename):
data = yaml.load(f, Loader=yaml.FullLoader)
env = Environment(loader=FileSystemLoader("."))
template = env.get_template("report.html.j2")
+ compute_diff_stats(data)
html = template.render(data=traverse_and_diff(data))
with open("report.html", "w") as f:
f.write(html)
diff --git a/report.html.j2 b/report.html.j2
index e3c6ab1..bec2589 100644
--- a/report.html.j2
+++ b/report.html.j2
@@ -36,23 +36,27 @@
Model Change Assessment Report
Current model: {{ data["metadata"]["new-revision"] }}
Previous model: {{ data["metadata"]["old-revision"] }}
- Object Changes
+ Object Changes {{ data["objects"].stats }}
Disclaimer: current version of model comparison engine uses a selected list of objects of interest and will not report object changes for object types that were considered as "out of scope". For the objects of interest however we can assure completness and correctness of comparison.
{% set LAYER = {"oa": "Operational Analysis", "sa": "System Analysis", "la": "Logical Architecture", "pa": "Physical Architecture"}%}
{% for layer in ["oa", "sa", "la", "pa"] %}
-
{{LAYER[layer]}}
+ {% set layer_data = data["objects"][layer] %}
+ {% if layer_data %}
+
{{LAYER[layer]}} {{ layer_data.stats }}
+ {% endif %}
+
- {% for obj_type in data["objects"][layer] %}
+ {% for obj_type in data["objects"][layer] if obj_type != "stats" %}
{% set obj_type_items = data["objects"][layer][obj_type] %}
-
{{obj_type}}
+
{{obj_type}} {{obj_type_items.stats}}
{% if "created" in obj_type_items %}
Created
{% for obj in obj_type_items["created"] %}
- - {{obj["name"]}}
+ - {{obj["display_name"]}}
{% endfor %}
@@ -64,12 +68,12 @@
{% for obj in obj_type_items["modified"] %}
{{ obj["display_name"] }}
- {% for change in obj["changes"] %}
+ {% for change in obj["attributes"] %}
- {{ change }}:
- {% if "diff" in obj["changes"][change] %}
- {{ obj["changes"][change]["diff"] }}
+ {% if "diff" in obj["attributes"][change] %}
+ {{ obj["attributes"][change]["diff"] }}
{% else %}
- {{ obj["changes"][change]["previous"] }} -> {{ obj["changes"][change]["current"] }}
+ {{ obj["attributes"][change]["previous"] }} -> {{ obj["attributes"][change]["current"] }}
{% endif %}
{% endfor %}
@@ -83,7 +87,7 @@
{% for obj in obj_type_items["deleted"] %}
- - {{obj["name"]}}
+ - {{obj["display_name"]}}
{% endfor %}