jinja2: use jinja2 for html template

This commit is contained in:
Laurier Loiselle 2023-01-23 15:47:45 -05:00
parent fc500b6d45
commit 641b44abf7
No known key found for this signature in database
GPG Key ID: 345920CC72089A3F
10 changed files with 209 additions and 204 deletions

View File

@ -4,3 +4,4 @@ pillow
pyyaml
setuptools
tabulate
jinja2

View File

@ -23,6 +23,7 @@ setup(
"pillow",
"pyyaml",
"tabulate",
"jinja2",
],
license="GPLv3",
keywords="cable connector hardware harness wiring wiring-diagram wiring-harness",

View File

@ -3,12 +3,12 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="generator" content="<!-- %generator% -->">
<title><!-- %title% --></title>
<meta name="generator" content=" {{ generator }}">
<title> {{ title }}</title>
<style>
body {
font-family: <!-- %fontname% -->;
background-color: <!-- %bgcolor% -->;
font-family: {{ fontname }};
background-color: {{ bgcolor }};
}
table, td, th, #frame {
@ -183,107 +183,29 @@
</head>
<body>
<div id="page">
<div id="frame" class="sheetsize_default">
<div id="frame" class={{ sheetsize_default }}>
<div id="diagram">
<div id="description">
<!-- %description% -->
{{ description }}
</div>
<!-- %diagram% -->
{{ diagram }}
<div id="notes">
<!-- %notes% -->
{{ notes }}
</div>
</div>
<div id="bom">
<!-- %bom_reversed% -->
{{ bom_reversed }}
</div>
<div id="titleblock">
<table>
<tr>
<td class="revno"><!-- %revisions_8% --></td>
<td class="changelog"><!-- %revisions_8_changelog% --></td>
<td class="date"><!-- %revisions_8_date% --></td>
<td class="name"><!-- %revisions_8_name% --></td>
<td class="process"></td>
<td class="date">Date</td>
<td class="name">Name</td>
<td class="title" colspan="3" rowspan="5"><!-- %title% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_7% --></td>
<td><!-- %revisions_7_changelog% --></td>
<td><!-- %revisions_7_date% --></td>
<td><!-- %revisions_7_name% --></td>
<td><!-- %authors_1% --></td>
<td><!-- %authors_1_date% --></td>
<td><!-- %authors_1_name% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_6% --></td>
<td><!-- %revisions_6_changelog% --></td>
<td><!-- %revisions_6_date% --></td>
<td><!-- %revisions_6_name% --></td>
<td><!-- %authors_2% --></td>
<td><!-- %authors_2_date% --></td>
<td><!-- %authors_2_name% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_5% --></td>
<td><!-- %revisions_5_changelog% --></td>
<td><!-- %revisions_5_date% --></td>
<td><!-- %revisions_5_name% --></td>
<td><!-- %authors_3% --></td>
<td><!-- %authors_3_date% --></td>
<td><!-- %authors_3_name% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_4% --></td>
<td><!-- %revisions_4_changelog% --></td>
<td><!-- %revisions_4_date% --></td>
<td><!-- %revisions_4_name% --></td>
<td colspan="2"></td>
<td></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_3% --></td>
<td><!-- %revisions_3_changelog% --></td>
<td><!-- %revisions_3_date% --></td>
<td><!-- %revisions_3_name% --></td>
<td class="company" colspan="3" rowspan="3"><!-- %company% --></td>
<td class="partno" colspan="2" rowspan="3"><!-- %pn% --></td>
<td class="sheetno" rowspan="2">Sheet<br /><!-- %sheet_current% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_2% --></td>
<td><!-- %revisions_2_changelog% --></td>
<td><!-- %revisions_2_date% --></td>
<td><!-- %revisions_2_name% --></td>
</tr>
<tr>
<td class="revno"><!-- %revisions_1% --></td>
<td><!-- %revisions_1_changelog% --></td>
<td><!-- %revisions_1_date% --></td>
<td><!-- %revisions_1_name% --></td>
<td class="sheetno">of <!-- %sheet_total% --></td>
</tr>
<tr>
<td>Rev</td>
<td>Changelog</td>
<td>Date</td>
<td>Name</td>
<td colspan="3"></td>
<td></td>
<td colspan="2"></td>
</tr>
</table>
</div> <!-- /titleblock -->
{{ titleblock }}
</div>
</div> <!-- /frame -->
</div> <!-- /page -->
</body>

View File

@ -2,7 +2,7 @@
<html lang="en"><head>
<meta charset="UTF-8">
<meta name="generator" content="<!-- %generator% -->">
<title><!-- %title% --></title>
<title>{{ title }}</title>
<style>
#bom table, th, td {
@ -20,26 +20,26 @@
}
</style>
</head><body style="font-family:<!-- %fontname% -->;background-color:<!-- %bgcolor% -->">
<h1><!-- %title% --></h1>
</head><body style="font-family:{{ fontname }};background-color:{{ bgcolor }}">
<h1>{{ title }}</h1>
<h2>Diagram</h2>
<div id="description">
<!-- %description% -->
{{ description }}
</div>
<div id="diagram">
<!-- %diagram% -->
{{ diagram }}
</div>
<div id="notes">
<!-- %notes% -->
{{ notes }}
</div>
<h2>Bill of Materials</h2>
<div id="bom">
<!-- %bom% -->
{{ bom }}
</div>
</body></html>

78
src/wireviz/templates/titleblock.html generated Normal file
View File

@ -0,0 +1,78 @@
<table>
<tr>
<td class="revno"> {{ revisions_8 }}</td>
<td class="changelog"> {{ revisions_8_changelog }}</td>
<td class="date"> {{ revisions_8_date }}</td>
<td class="name"> {{ revisions_8_name }}</td>
<td class="process"></td>
<td class="date">Date</td>
<td class="name">Name</td>
<td class="title" colspan="3" rowspan="5"> {{ title }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_7 }}</td>
<td> {{ revisions_7_changelog }}</td>
<td> {{ revisions_7_date }}</td>
<td> {{ revisions_7_name }}</td>
<td> {{ authors_1 }}</td>
<td> {{ authors_1_date }}</td>
<td> {{ authors_1_name }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_6 }}</td>
<td> {{ revisions_6_changelog }}</td>
<td> {{ revisions_6_date }}</td>
<td> {{ revisions_6_name }}</td>
<td> {{ authors_2 }}</td>
<td> {{ authors_2_date }}</td>
<td> {{ authors_2_name }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_5 }}</td>
<td> {{ revisions_5_changelog }}</td>
<td> {{ revisions_5_date }}</td>
<td> {{ revisions_5_name }}</td>
<td> {{ authors_3 }}</td>
<td> {{ authors_3_date }}</td>
<td> {{ authors_3_name }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_4 }}</td>
<td> {{ revisions_4_changelog }}</td>
<td> {{ revisions_4_date }}</td>
<td> {{ revisions_4_name }}</td>
<td colspan="2"></td>
<td></td>
</tr>
<tr>
<td class="revno"> {{ revisions_3 }}</td>
<td> {{ revisions_3_changelog }}</td>
<td> {{ revisions_3_date }}</td>
<td> {{ revisions_3_name }}</td>
<td class="company" colspan="3" rowspan="3"> {{ company }}</td>
<td class="partno" colspan="2" rowspan="3"> {{ pn }}</td>
<td class="sheetno" rowspan="2">Sheet<br /> {{ sheet_current }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_2 }}</td>
<td> {{ revisions_2_changelog }}</td>
<td> {{ revisions_2_date }}</td>
<td> {{ revisions_2_name }}</td>
</tr>
<tr>
<td class="revno"> {{ revisions_1 }}</td>
<td> {{ revisions_1_changelog }}</td>
<td> {{ revisions_1_date }}</td>
<td> {{ revisions_1_name }}</td>
<td class="sheetno">of {{ sheet_total }}</td>
</tr>
<tr>
<td>Rev</td>
<td>Changelog</td>
<td>Date</td>
<td>Name</td>
<td colspan="3"></td>
<td></td>
<td colspan="2"></td>
</tr>
</table>

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
import argparse
from click.testing import CliRunner
import os
import sys
from pathlib import Path
@ -9,26 +10,27 @@ from pathlib import Path
script_path = Path(__file__).absolute()
sys.path.insert(0, str(script_path.parent.parent.parent)) # to find wireviz module
from wireviz import APP_NAME, __version__, wireviz
from wireviz import APP_NAME, __version__
from wireviz.wv_cli import cli
from wireviz.wv_utils import open_file_append, open_file_read, open_file_write
dir = script_path.parent.parent.parent.parent
base_dir = script_path.parent.parent.parent.parent
readme = "readme.md"
groups = {
"examples": {
"path": dir / "examples",
"path": base_dir / "examples",
"prefix": "ex",
readme: [], # Include no files
"title": "Example Gallery",
},
"tutorial": {
"path": dir / "tutorial",
"path": base_dir / "tutorial",
"prefix": "tutorial",
readme: ["md", "yml"], # Include .md and .yml files
"title": f"{APP_NAME} Tutorial",
},
"demos": {
"path": dir / "examples",
"path": base_dir / "examples",
"prefix": "demo",
},
}
@ -51,6 +53,7 @@ def collect_filenames(description, groupkey, ext_list):
def build_generated(groupkeys):
runner = CliRunner()
for key in groupkeys:
# preparation
path = groups[key]["path"]
@ -62,9 +65,13 @@ def build_generated(groupkeys):
out.write(f'# {groups[key]["title"]}\n\n')
# collect and iterate input YAML files
for yaml_file in collect_filenames("Building", key, input_extensions):
print(f' "{yaml_file}"')
wireviz.parse(yaml_file, output_formats=("gv", "html", "png", "svg", "tsv"))
res = runner.invoke(cli, args=[
'--format', 'ghpst',
str(yaml_file)
])
if res.exit_code != 0:
raise RuntimeError(f'Cli failed for {yaml_file} with result: {res}') from res.exception
if build_readme:
i = "".join(filter(str.isdigit, yaml_file.stem))

View File

@ -111,8 +111,7 @@ def bom_list(bom):
all_designators = sorted(entry["designators"])
if len(all_designators) > MAX_DESIGNATORS:
all_designators = all_designators[:MAX_DESIGNATORS] + ['...']
all_designators = all_designators[:MAX_DESIGNATORS] + ["..."]
cells = [
entry["id"],
@ -129,8 +128,8 @@ def bom_list(bom):
hash.partnumbers.pn,
hash.partnumbers.manufacturer,
hash.partnumbers.mpn,
None, #hash.partnumbers.supplier,
None, #hash.partnumbers.spn,
None, # hash.partnumbers.supplier,
None, # hash.partnumbers.spn,
]
)
else:

View File

@ -129,11 +129,10 @@ def cli(file, format, prepend, output_dir, output_name, version):
raise Exception(f"Path is not a file:\n{file}")
extra_metadata = {}
extra_metadata['name'] = file.stem
extra_metadata['sheet_total'] = len(filepaths)
extra_metadata['sheet_current'] = sheet_current
sheet_current +=1
extra_metadata["name"] = file.stem
extra_metadata["sheet_total"] = len(filepaths)
extra_metadata["sheet_current"] = sheet_current
sheet_current += 1
# file_out = file.with_suffix("") if not output_file else output_file
_output_dir = file.parent if not output_dir else output_dir

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
import logging
from collections import namedtuple
from dataclasses import dataclass, field
from enum import Enum
from itertools import zip_longest
import logging
from typing import Any, Dict, List, Optional, Tuple, Union
from wireviz.wv_bom import (
@ -576,7 +576,9 @@ class Cable(TopLevelGraphicalComponent):
def gauge_str(self):
if not self.gauge:
return None
number = int(self.gauge.number) if self.gauge.unit == 'AWG' else self.gauge.number
number = (
int(self.gauge.number) if self.gauge.unit == "AWG" else self.gauge.number
)
actual_gauge = f"{number} {self.gauge.unit}"
actual_gauge = actual_gauge.replace("mm2", "mm\u00B2")
return actual_gauge
@ -631,59 +633,60 @@ class Cable(TopLevelGraphicalComponent):
return desc
belden_color = {
'BN': '001',
'RD': '002',
'OG': '003',
'YE': '004',
'GN': '005',
'TQ': '006', # (For Belden: light blue. For WireViz: turquoise)
'VT': '007',
'GY': '008',
'WH': '009',
'BK': '010',
'BG': '011',
'PK': '012',
'BU': '013',
'BKRD':'015', # (for Belden: white/red)
'BKGN':'016', # (for Belden: white/green)
'BKYE':'017', # (for Belden: white/yellow)
'BKBU':'018', # (for Belden: white/blue)
'BKBN':'019', # (for Belden: white/brown)
'BKOG':'020', # (for Belden: white/orange)
'BKGY':'021', # (for Belden: white/gray)
'BKVT':'022', # (for Belden: white/purple)
"BN": "001",
"RD": "002",
"OG": "003",
"YE": "004",
"GN": "005",
"TQ": "006", # (For Belden: light blue. For WireViz: turquoise)
"VT": "007",
"GY": "008",
"WH": "009",
"BK": "010",
"BG": "011",
"PK": "012",
"BU": "013",
"BKRD": "015", # (for Belden: white/red)
"BKGN": "016", # (for Belden: white/green)
"BKYE": "017", # (for Belden: white/yellow)
"BKBU": "018", # (for Belden: white/blue)
"BKBN": "019", # (for Belden: white/brown)
"BKOG": "020", # (for Belden: white/orange)
"BKGY": "021", # (for Belden: white/gray)
"BKVT": "022", # (for Belden: white/purple)
# (1) Why use BKRD instead of WHRD, since Belden only sells white/red?
# - WHRD is impractical to use in Wireviz (white wire sides on white background does not help with identification)
# - BKRD is clearly distinguishable, and comes handy to use in Wireviz, as the representation of a GND rail associated (even twisted, if applicable) with a specific RD signal wire.
# (2) For all wire colors see:
# https://www.belden.com/dfsmedia/f1e38517e0cd4caa8b1acb6619890f5e/7806-source/options/view/cabling-solutions-for-industrial-applications-catalog-belden-09-2020#page=153
}
belden_tfe_base_mpn = {
# Leftmost in list is the prefered MPN
# NOTE (lal 2022-12-20): this prefered MPN is arbitrary ATM
'16 AWG': ['83030', '83010'],
'18 AWG': ['83029', '83009'],
'20 AWG': ['83028', '83027', '83007', '83008'],
'22 AWG': ['83025', '83026', '83005', '83006', '83049', '83050'],
'24 AWG': ['83023', '83003', '83004', '83047', '83048'],
'26 AWG': ['83002', '83046'],
'28 AWG': ['83001', '83045'],
'30 AWG': ['83000', '83043'],
'32 AWG': ['83041'],
"16 AWG": ["83030", "83010"],
"18 AWG": ["83029", "83009"],
"20 AWG": ["83028", "83027", "83007", "83008"],
"22 AWG": ["83025", "83026", "83005", "83006", "83049", "83050"],
"24 AWG": ["83023", "83003", "83004", "83047", "83048"],
"26 AWG": ["83002", "83046"],
"28 AWG": ["83001", "83045"],
"30 AWG": ["83000", "83043"],
"32 AWG": ["83041"],
# see: https://www.belden.com/dfsmedia/f1e38517e0cd4caa8b1acb6619890f5e/7806-source/options/view/cabling-solutions-for-industrial-applications-catalog-belden-09-2020#page=136
}
@property
def is_belden(self):
if 'belden' in self.manufacturer.lower():
if "belden" in self.manufacturer.lower():
return True
return False
def get_belden_color(self, color):
if color not in self.belden_color:
logging.warn(f'No color found in belden colors {list(self.belden_color.keys())} matching {self.color}, defaulting to BK')
return self.belden_color['BK']
logging.warn(
f"No color found in belden colors {list(self.belden_color.keys())} matching {self.color}, defaulting to BK"
)
return self.belden_color["BK"]
return self.belden_color[color]
def gen_belden_cable_with_alternate(self, color):
@ -691,16 +694,18 @@ class Cable(TopLevelGraphicalComponent):
try:
parts = self.belden_tfe_base_mpn[self.gauge_str]
except KeyError:
raise ValueError(f'Couldn\'t find a belden TFE wire for wire of {self.gauge_str}')
raise ValueError(
f"Couldn't find a belden TFE wire for wire of {self.gauge_str}"
)
color = self.get_belden_color(color)
if not color:
raise ValueError(f'Failed to find a color for property: {self.description}')
raise ValueError(f"Failed to find a color for property: {self.description}")
# Create the list of mpn
roll_length = 100
mpn_list = [f'{mpn} {color}{roll_length}' for mpn in parts]
mpn_list = [f"{mpn} {color}{roll_length}" for mpn in parts]
main_part = mpn_list[0]
alternates = mpn_list[1:] if len(mpn_list) > 1 else []
@ -712,11 +717,15 @@ class Cable(TopLevelGraphicalComponent):
main_part, alternates = self.gen_belden_cable_with_alternate(color)
return main_part
if alternates:
logging.info(f'Alternate part{"s" if len(alternates) > 1 else ""} available for {self.gauge_str}, color {self.color}: {alternates}')
logging.info(
f'Alternate part{"s" if len(alternates) > 1 else ""} available for {self.gauge_str}, color {self.color}: {alternates}'
)
else:
logging.info(f'Not updating part for manufacturer {self.manufacturer}, only "belden" supported')
logging.info(
f'Not updating part for manufacturer {self.manufacturer}, only "belden" supported'
)
else:
logging.info(f'Not updating part, no manufacturer provided')
logging.info(f"Not updating part, no manufacturer provided")
return mpn
def _get_wire_partnumber(self, idx, color) -> PartNumberInfo:
@ -725,8 +734,8 @@ class Cable(TopLevelGraphicalComponent):
# TODO: possibly make more robust/elegant
if self.category == "bundle":
manufacturer = _get_correct_element(self.partnumbers.manufacturer, idx),
mpn = _get_correct_element(self.partnumbers.mpn, idx),
manufacturer = (_get_correct_element(self.partnumbers.manufacturer, idx),)
mpn = (_get_correct_element(self.partnumbers.mpn, idx),)
if color is not None:
mpn = self.get_mpn_if_belden(manufacturer, mpn, color.code_en)

View File

@ -5,15 +5,12 @@ import re
from pathlib import Path
from typing import Dict, List, Union
import jinja2
import wireviz # for doing wireviz.__file__
from wireviz import APP_NAME, APP_URL, __version__
from wireviz.wv_dataclasses import Metadata, Options
from wireviz.wv_utils import (
html_line_breaks,
open_file_read,
open_file_write,
smart_file_resolve,
)
from wireviz.wv_utils import html_line_breaks, open_file_read, open_file_write
mime_subtype_replacements = {"jpg": "jpeg", "tif": "tiff"}
@ -62,27 +59,24 @@ def embed_svg_images_file(
filename_out.replace(filename_in)
def get_template_html(template_name):
template_file_path = jinja2.FileSystemLoader(
Path(wireviz.__file__).parent / "templates"
)
jinja_env = jinja2.Environment(loader=template_file_path)
return jinja_env.get_template(template_name + ".html")
def generate_html_output(
filename: Union[str, Path],
bom: List[List[str]],
metadata: Metadata,
options: Options,
):
# load HTML template
templatename = metadata.get("template", {}).get("name")
if templatename:
# if relative path to template was provided,
# check directory of YAML file first, fall back to built-in template directory
templatefile = smart_file_resolve(
f"{templatename}.html",
[Path(filename).parent, Path(__file__).parent / "templates"],
)
else:
# fall back to built-in simple template if no template was provided
templatefile = Path(wireviz.__file__).parent / "templates/simple.html"
html = open_file_read(templatefile).read()
print("Generating html output")
template_name = metadata.get("template", {}).get("name", "simple")
page_template = get_template_html(template_name)
# embed SVG diagram
with open_file_read(f"{filename}.tmp.svg") as file:
@ -122,51 +116,46 @@ def generate_html_output(
)
if metadata:
sheet_current = metadata['sheet_current']
sheet_total = metadata['sheet_total']
sheet_current = metadata["sheet_current"]
sheet_total = metadata["sheet_total"]
else:
sheet_current = 1
sheet_total = 1
# prepare simple replacements
replacements = {
"<!-- %generator% -->": f"{APP_NAME} {__version__} - {APP_URL}",
"<!-- %fontname% -->": options.fontname,
"<!-- %bgcolor% -->": options.bgcolor.html,
"<!-- %diagram% -->": svgdata,
"<!-- %bom% -->": bom_html,
"<!-- %bom_reversed% -->": bom_html_reversed,
"<!-- %sheet_current% -->": sheet_current,
"<!-- %sheet_total% -->": sheet_total,
"title": "pizza",
"generator": f"{APP_NAME} {__version__} - {APP_URL}",
"fontname": options.fontname,
"bgcolor": options.bgcolor.html,
"diagram": svgdata,
"bom": bom_html,
"bom_reversed": bom_html_reversed,
"sheet_current": sheet_current,
"sheet_total": sheet_total,
}
# prepare metadata replacements
if metadata:
for item, contents in metadata.items():
if isinstance(contents, (str, int, float)):
replacements[f"<!-- %{item}% -->"] = html_line_breaks(str(contents))
replacements[str(item)] = html_line_breaks(str(contents))
elif isinstance(contents, Dict): # useful for authors, revisions
for index, (category, entry) in enumerate(contents.items()):
if isinstance(entry, Dict):
replacements[f"<!-- %{item}_{index+1}% -->"] = str(category)
replacements[f"{item}_{index+1}"] = str(category)
for entry_key, entry_value in entry.items():
replacements[
f"<!-- %{item}_{index+1}_{entry_key}% -->"
f"{item}_{index+1}_{entry_key}"
] = html_line_breaks(str(entry_value))
replacements['"sheetsize_default"'] = '"{}"'.format(
metadata.get("template", {}).get("sheetsize", "")
)
replacements[
"sheetsize_default"
] = f'{metadata.get("template", {}).get("sheetsize", "sheetsize_default")}'
# include quotes so no replacement happens within <style> definition
# perform replacements
# regex replacement adapted from:
# https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729
# prepare titleblock
titleblock_template = get_template_html("titleblock")
replacements["titleblock"] = titleblock_template.render(replacements)
# longer replacements first, just in case
replacements_sorted = sorted(replacements, key=len, reverse=True)
replacements_escaped = map(re.escape, replacements_sorted)
pattern = re.compile("|".join(replacements_escaped))
html = pattern.sub(lambda match: replacements[match.group(0)], html)
open_file_write(f"{filename}.html").write(html)
page_rendered = page_template.render(replacements)
open_file_write(f"{filename}.html").write(page_rendered)