From befc284184bb0b9a91d03e9ec5143af707e13a3f Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Thu, 18 Apr 2019 22:23:08 +0200 Subject: [PATCH] Added basic HTML generator from schema --- retribute_me/cli.py | 53 +++++++++++++++++++++- retribute_me/models.py | 28 ++++++++++++ retribute_me/templates/profile.jinja2 | 64 +++++++++++++++++++++++++++ schemas/0.1/example.json | 2 + schemas/0.1/schema.json | 27 ++++++++--- 5 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 retribute_me/models.py create mode 100644 retribute_me/templates/profile.jinja2 diff --git a/retribute_me/cli.py b/retribute_me/cli.py index 06cbdb5..7a407c2 100644 --- a/retribute_me/cli.py +++ b/retribute_me/cli.py @@ -1,9 +1,16 @@ import click +import jinja2 import json import jsonschema +import os + +from . import models from . import schemas +PROJECT_DIR = os.path.abspath(os.path.dirname(__file__)) + + @click.group() def cli(): pass @@ -22,9 +29,51 @@ def validate(document, version): except KeyError: version = schemas.latest_schema_version - schema = schemas.get(version) + try: + schema = schemas.get(version) + except FileNotFoundError: + raise click.ClickException("{} is not a valid version".format(version)) jsonschema.validate(instance=payload, schema=schema) - click.echo("Schema is valid!") + click.echo("Document is valid!") + + +def get_template_context(document): + context = {"doc": document, "version": document["version"]} + context["sorted_means"] = [ + models.Mean(**mean_data) + for mean_data in sorted( + document.get("means", []), key=lambda v: v["weight"], reverse=True + ) + ] + context["sorted_identities"] = [ + models.Identity(**identity_data) + for identity_data in sorted( + document.get("identities", []), key=lambda v: v["weight"], reverse=True + ) + ] + return context + + +@cli.command() +@click.argument("document", type=click.File("r")) +@click.argument("output", type=click.File("w")) +@click.option("--template", "-t", type=click.File("r")) +def html(document, output, template): + content = document.read() + document = json.loads(content) + context = get_template_context(document) + if not template: + default_template = os.path.join(PROJECT_DIR, "templates", "profile.jinja2") + with open(default_template, "r") as f: + template_content = f.read() + else: + template_content = template.read() + + t = jinja2.Template( + "{% autoescape true %}" + template_content + "{% endautoescape %}", + undefined=jinja2.StrictUndefined, + ) + click.echo(t.render(**context)) if __name__ == "__main__": diff --git a/retribute_me/models.py b/retribute_me/models.py new file mode 100644 index 0000000..1590140 --- /dev/null +++ b/retribute_me/models.py @@ -0,0 +1,28 @@ +class Identity(object): + def __init__(self, provider, id, weight, name=None, summary=None, url=None): + self.provider = provider + self.id = id + self.summary = summary + self.weight = weight + self.name = name + self.url = url + + def get_full_name(self): + if self.name: + return self.name + return "{} on {}".format(self.id, self.provider) + + +class Mean(object): + def __init__(self, provider, id, weight, name=None, summary=None, url=None): + self.provider = provider + self.id = id + self.summary = summary + self.weight = weight + self.name = name + self.url = url + + def get_full_name(self): + if self.name: + return self.name + return "{} on {}".format(self.id, self.provider) diff --git a/retribute_me/templates/profile.jinja2 b/retribute_me/templates/profile.jinja2 new file mode 100644 index 0000000..c80aa43 --- /dev/null +++ b/retribute_me/templates/profile.jinja2 @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width,initial-scale=1.0"> + <title>{{ doc['title'] }}</title> + {%- if "summary" in doc %} + <meta name="description" content="{{ doc['summary'] }}" /> + {% endif %} + </head> + <body> + <header> + <h1>{{ doc['title'] }}</h1> + {%- if "summary" in doc %} + <p>{{ doc['summary'] }}</p> + {% endif %} + </header> + <main> + {%- if sorted_identities|length > 0 %} + <section class="identity"> + <h2>My other identities</h2> + {% for identity in sorted_identities %} + <article> + <h3> + {%- if identity.url %} + <a href="{{ identity.url }}" rel="me noopener"> + {{ identity.get_full_name() }} + </a> + {%- else %} + {{ identity.get_full_name() }} + {%- endif %} + </h3> + {%- if identity.summary %} + <p>{{ identity.summary }}</p> + {%- endif %} + </article> + {% endfor %} + </section> + {%- endif %} + {%- if sorted_means|length > 0 %} + <section> + <h2>Support me!</h2> + {% for mean in sorted_means %} + <article class="mean"> + <h3> + {%- if mean.url %} + <a href="{{ mean.url }}" rel="me noopener"> + {{ mean.get_full_name() }} + </a> + {%- else %} + {{ mean.get_full_name() }} + {%- endif %} + </h3> + {%- if mean.summary %} + <p>{{ mean.summary }}</p> + {%- endif %} + </article> + {% endfor %} + </section> + {%- endif %} + </main> + </body> +</html> diff --git a/schemas/0.1/example.json b/schemas/0.1/example.json index dc7ed6d..f30ef32 100644 --- a/schemas/0.1/example.json +++ b/schemas/0.1/example.json @@ -2,6 +2,7 @@ "version": "0.1", "id": "https://retribute.me/@Alice.json", "url": "https://retribute.me/@Alice.html", + "title": "Caring is loving", "summary": "My name is Alice, I produce Creative Commons electro music. I need your support to continue working on my audio pieces!", "identities": [ { @@ -19,6 +20,7 @@ { "provider": "custom", "id": "personal-website", + "name": "My personal website", "url": "https://alice.me", "weight": 3 } diff --git a/schemas/0.1/schema.json b/schemas/0.1/schema.json index 37460a3..c901a95 100644 --- a/schemas/0.1/schema.json +++ b/schemas/0.1/schema.json @@ -6,6 +6,7 @@ "additionalProperties": false, "required": [ "id", + "title", "version", "means" ], @@ -30,26 +31,38 @@ "https://retribute.me/@Alice.html" ] }, + "title": { + "description": "A human readable title to go with the profile", + "type": "string" + }, "summary": { - "description": "A human readable exaplanation to go with the profile", + "description": "A human readable explanation to go with the profile", "type": "string" }, "means": { "description": "A list of retribution means", "type": "array", - "items": { "$ref": "#/definitions/mean" } + "items": { + "$ref": "#/definitions/mean" + } }, "identities": { "description": "A list of identities lined to the profile", "type": "array", - "items": { "$ref": "#/definitions/identity" } + "items": { + "$ref": "#/definitions/identity" + } } }, "definitions": { "identity": { "description": "A single retribution mean. The provider and id must form a unique combination", "type": "object", - "required": ["provider", "id", "weight"], + "required": [ + "provider", + "id", + "weight" + ], "minItems": 0, "properties": { "provider": { @@ -89,7 +102,11 @@ "mean": { "description": "A single retribution mean. The provider and id must form a unique combination", "type": "object", - "required": ["provider", "id", "weight"], + "required": [ + "provider", + "id", + "weight" + ], "minItems": 1, "properties": { "provider": { -- GitLab