diff --git a/pyproject.toml b/pyproject.toml
index b8e7332..ddbef7a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -57,3 +57,6 @@ dev = [
"Sphinx>=1.8.5",
"twine>=1.14.0",
]
+
+[tool.setuptools.package-data]
+tro_utils = ["*.jinja2"]
diff --git a/tro_utils/cli.py b/tro_utils/cli.py
index 628c8d2..301c73a 100644
--- a/tro_utils/cli.py
+++ b/tro_utils/cli.py
@@ -1,4 +1,5 @@
"""Console script for tro_utils."""
+import os
import sys
import click
@@ -6,6 +7,37 @@
from . import TRPAttribute
from .tro_utils import TRO
+_TEMPLATES = {
+ "default": {
+ "description": "Default pretty template by Craig Willis",
+ "filename": "default.jinja2",
+ },
+}
+
+
+class StringOrPath(click.ParamType):
+ """Custom parameter type to accept either a valid string or a file path."""
+
+ name = "string_or_path"
+
+ def __init__(self, templates=None):
+ self.valid_strings = templates.keys()
+
+ def convert(self, value, param, ctx):
+ # Check if the value is in the allowed string set
+ if value in self.valid_strings:
+ return value
+ # Check if the value is a valid path
+ elif os.path.exists(value) and os.path.isfile(value):
+ return value
+ else:
+ self.fail(
+ f"'{value}' is neither a valid option ({', '.join(self.valid_strings)}) "
+ f"nor a valid file path.",
+ param,
+ ctx,
+ )
+
@click.group()
@click.option(
@@ -189,12 +221,20 @@ def sign(ctx):
@cli.command(help="Generate a report of the TRO", name="report")
@click.option(
- "--template", "-t", type=click.Path(), required=True, help="Template file"
+ "--template",
+ "-t",
+ type=StringOrPath(_TEMPLATES),
+ required=True,
+ help=f"Template file or one of the following: {', '.join(_TEMPLATES.keys())}",
)
@click.option("--output", "-o", type=click.Path(), required=True, help="Output file")
@click.pass_context
def generate_report(ctx, template, output):
declaration = ctx.parent.params.get("declaration")
+ if template in _TEMPLATES:
+ template = os.path.join(
+ os.path.dirname(__file__), _TEMPLATES[template]["filename"]
+ )
tro = TRO(
filepath=declaration,
)
diff --git a/tro_utils/default.jinja2 b/tro_utils/default.jinja2
new file mode 100644
index 0000000..1ff41a4
--- /dev/null
+++ b/tro_utils/default.jinja2
@@ -0,0 +1,69 @@
+#
TRO Report
+
+
+## TRO Information
+| Property | Value |
+| -------- | ----- |
+| Name | {{ tro["schema:name"] }} |
+| Description | {{ tro["schema:description"] }} |
+| Created by | {{ tro["schema:creator"] }} |
+| Created date | {{ tro["schema:dateCreated"] }} |
+
+
+## TRACE System Information
+
+This TRO was generated by the following TRACE System:
+
+| Property | Value |
+| -------- | ----- |
+| Name | {{ tro["trov:wasAssembledBy"]["trov:name"] }} |
+| Description | {{ tro["trov:wasAssembledBy"]["trov:description"] }} |
+| Owner | {{ tro["trov:wasAssembledBy"]["trov:owner"] }} |
+| Contact | {{ tro["trov:wasAssembledBy"]["trov:contact"] }} |
+| URL | {{ tro["trov:wasAssembledBy"]["trov:url"] }} |
+
+
+Show Public Key
+{{ tro["trov:wasAssembledBy"]["trov:publicKey"] }}
+
+
+### Capabilities
+
+| Capability | Description |
+| ----------- | ------------ |
+{%- for capability in tro["trov:wasAssembledBy"].get("trov:hasCapability", []) %}
+| {{ capability.get("@type", "") }} | {{ capability.get("trov:description", "") }} |
+{%- endfor %}
+
+## Trusted Research Performances
+
+A Trusted Research Performance (TRP) captures the execution of a process in the context of a TRACE system. Typically, a TRP would take as input one or more sets of files (input arrangements) and produce another set of files (output arrangements).
+
+
+
+| Description | Accessed | Contributed |
+| ----------- | ------------ | ------------ |
+{%- for trp in tro["trps"] %}
+| {{ trp["description"] }} | {{ trp["accessed"] }} | {{ trp["contributed"] }} |
+{%- endfor %}
+
+
+## Arrangements
+
+Arrangements define how artifacts, typically files, are organized before and after each TRP. Artifacts are defined by their location and a checksum of their contents. Artifacts may be local or remote, defined by an URI. They may be included or excluded from the associated archive.
+
+{%- for arrangement in tro["arrangements"].keys() %}
+
+### {{ tro["arrangements"][arrangement]["name"] }}
+
+| Artifact | SHA-256 | Status |
+| -------- | -------- | ------ |
+{%- for location in tro["arrangements"][arrangement]["artifacts"] %}
+ {%- if tro["arrangements"][arrangement]["artifacts"][location].get("excluded") %}
+| ~~`{{ location }}`~~ | {{ tro["arrangements"][arrangement]["artifacts"][location]["sha256"] | truncate(32) }} | Excluded due to {{ tro["arrangements"][arrangement]["artifacts"][location]["excluded"] }} |
+ {%- else %}
+| `{{ location }}` | {{ tro["arrangements"][arrangement]["artifacts"][location]["sha256"] | truncate(32) }} | {{ tro["arrangements"][arrangement]["artifacts"][location]["status"] }} |
+
+ {%- endif %}
+{%- endfor %}
+{%- endfor %}
diff --git a/tro_utils/tro_utils.py b/tro_utils/tro_utils.py
index 2b87461..55eaba6 100644
--- a/tro_utils/tro_utils.py
+++ b/tro_utils/tro_utils.py
@@ -1,4 +1,5 @@
"""Main module."""
+import base64
import hashlib
import json
import os
@@ -393,7 +394,6 @@ def add_performance(
def generate_report(self, template, report):
graph = self.data["@graph"][0]
- trs = graph["trov:wasAssembledBy"]
composition = {
obj["@id"]: obj for obj in graph["trov:hasComposition"]["trov:hasArtifact"]
}
@@ -429,6 +429,8 @@ def generate_report(self, template, report):
dot.attr("node", shape="box3d", style="filled, rounded", fillcolor="#D6FDD0")
+ if isinstance(graph["trov:hasPerformance"], dict):
+ graph["trov:hasPerformance"] = [graph["trov:hasPerformance"]]
for trp in graph["trov:hasPerformance"]:
description = trp["rdfs:comment"]
accessed = arrangements[trp["trov:accessedArrangement"]["@id"]]["name"]
@@ -437,7 +439,8 @@ def generate_report(self, template, report):
dot.edge(accessed, description)
dot.edge(description, contributed)
- dot.render("workflow", directory=".", cleanup=True, format="png")
+ png_bytes = dot.pipe(format="png")
+ png_base64 = base64.b64encode(png_bytes).decode("utf-8")
# Detect changes between arrangements
# Which files were added? Which files changed?
@@ -458,26 +461,8 @@ def generate_report(self, template, report):
arrangements[keys[n]]["artifacts"][location]["status"] = "Created"
data = {
- "name": graph.get("schema:name", "No name provided"),
- "description": graph.get("schema:description", "No Description provided"),
- "creator": graph.get("schema:creator", "No creator provided"),
- "dateCreated": graph.get("schema:dateCreated", "No date provided"),
- "trs": {
- "publicKey": trs.get("trov:publicKey"),
- "name": trs.get("schema:name", ""),
- "comment": trs["rdfs:comment"],
- "publisher": trs.get("schema:publisher", ""),
- "description": trs.get("schema:description", ""),
- "email": trs.get("schema:email", ""),
- "url": trs.get("schema:url", ""),
- "capabilities": [
- {
- "name": _.get("trov:name", _["@type"]),
- "description": _.get("trov:description", ""),
- }
- for _ in trs["trov:hasCapability"]
- ],
- },
+ **graph,
+ "workflow_diagram": png_base64,
"trps": [
{
"id": trp["@id"],