Skip to content

Commit

Permalink
Add basic options and metadata (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
kvid authored Aug 25, 2021
1 parent e212fc9 commit 92354e6
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 41 deletions.
55 changes: 55 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
## Main sections

```yaml
metadata: # dictionary of meta-information describing the harness
<key> : <value> # any number of key value pairs (see below)
...
options: # dictionary of common attributes for the whole harness
<str> : <value> # optional harness attributes (see below)
...
connectors: # dictionary of all used connectors
<str> : # unique connector designator/name
... # connector attributes (see below)
Expand Down Expand Up @@ -31,6 +37,55 @@ additional_bom_items: # custom items to add to BOM

```

## Metadata entries

```yaml
# Meta-information describing the harness

# Each key/value pair replaces all key references in
# the HTML output template with the belonging value.
# Typical keys are 'title', 'description', and 'notes',
# but any key is accepted. Unused keys are ignored.
<key> : <value> # Any valid YAML syntax is accepted
# If no value is specified for 'title', then the
# output filename without extension is used.
```

## Options

```yaml
# Common attributes for the whole harness.
# All entries are optional and have default values.

# Background color of diagram and HTML output
bgcolor: <color> # Default = 'WH'

# Background color of other diagram elements
bgcolor_node: <color> # Default = 'WH'
bgcolor_connector: <color> # Default = bgcolor_node
bgcolor_cable: <color> # Default = bgcolor_node
bgcolor_bundle: <color> # Default = bgcolor_cable

# How to display colors as text in the diagram
# 'full' : Lowercase full color name
# 'FULL' : Uppercase full color name
# 'hex' : Lowercase hexadecimal values
# 'HEX' : Uppercase hexadecimal values
# 'short': Lowercase short color name
# 'SHORT': Uppercase short color name
# 'ger' : Lowercase short German color name
# 'GER' : Uppercase short German color name
color_mode: <str> # Default = 'SHORT'

# Fontname to use in diagram and HTML output
fontname: <str> # Default = 'arial'

# If True, show only a BOM entry reference together with basic info
# about additional components inside the diagram node (connector/cable box).
# If False, show all info about additional components inside the diagram node.
mini_bom_mode: <bool> # Default = True
```
## Connector attributes
```yaml
Expand Down
30 changes: 29 additions & 1 deletion src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Optional, List, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union
from dataclasses import dataclass, field, InitVar
from pathlib import Path

Expand All @@ -20,6 +20,7 @@
CableMultiplier = PlainText # = Literal['wirecount', 'terminations', 'length', 'total_length']
ImageScale = PlainText # = Literal['false', 'true', 'width', 'height', 'both']
Color = PlainText # Two-letter color name = Literal[wv_colors._color_hex.keys()]
ColorMode = PlainText # = Literal['full', 'FULL', 'hex', 'HEX', 'short', 'SHORT', 'ger', 'GER']
ColorScheme = PlainText # Color scheme name = Literal[wv_colors.COLOR_CODES.keys()]

# Type combinations
Expand All @@ -30,6 +31,33 @@
NoneOrMorePinIndices = Union[PinIndex, Tuple[PinIndex, ...], None] # None, one, or a tuple of zero-based pin indices
OneOrMoreWires = Union[Wire, Tuple[Wire, ...]] # One or a tuple of wires

# Metadata can contain whatever is needed by the HTML generation/template.
MetadataKeys = PlainText # Literal['title', 'description', 'notes', ...]
class Metadata(dict):
pass


@dataclass
class Options:
fontname: PlainText = 'arial'
bgcolor: Color = 'WH'
bgcolor_node: Optional[Color] = 'WH'
bgcolor_connector: Optional[Color] = None
bgcolor_cable: Optional[Color] = None
bgcolor_bundle: Optional[Color] = None
color_mode: ColorMode = 'SHORT'
mini_bom_mode: bool = True

def __post_init__(self):
if not self.bgcolor_node:
self.bgcolor_node = self.bgcolor
if not self.bgcolor_connector:
self.bgcolor_connector = self.bgcolor_node
if not self.bgcolor_cable:
self.bgcolor_cable = self.bgcolor_node
if not self.bgcolor_bundle:
self.bgcolor_bundle = self.bgcolor_cable


@dataclass
class Image:
Expand Down
46 changes: 27 additions & 19 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
from graphviz import Graph
from collections import Counter
from typing import List, Union
from dataclasses import dataclass
from pathlib import Path
from itertools import zip_longest
import re

from wireviz import wv_colors, __version__, APP_NAME, APP_URL
from wireviz.DataClasses import Connector, Cable
from wireviz.wv_colors import get_color_hex
from wireviz.DataClasses import Metadata, Options, Connector, Cable
from wireviz.wv_colors import get_color_hex, translate_color
from wireviz.wv_gv_html import nested_html_table, html_colorbar, html_image, \
html_caption, remove_links, html_line_breaks
from wireviz.wv_bom import manufacturer_info_field, component_table_entry, \
Expand All @@ -20,11 +21,12 @@
open_file_read, open_file_write


@dataclass
class Harness:
metadata: Metadata
options: Options

def __init__(self):
self.color_mode = 'SHORT'
self.mini_bom_mode = True
def __post_init__(self):
self.connectors = {}
self.cables = {}
self._bom = [] # Internal Cache for generated bom
Expand Down Expand Up @@ -91,18 +93,19 @@ def create_graph(self) -> Graph:
dot = Graph()
dot.body.append(f'// Graph generated by {APP_NAME} {__version__}')
dot.body.append(f'// {APP_URL}')
font = 'arial'
dot.attr('graph', rankdir='LR',
ranksep='2',
bgcolor='white',
bgcolor=wv_colors.translate_color(self.options.bgcolor, "HEX"),
nodesep='0.33',
fontname=font)
dot.attr('node', shape='record',
fontname=self.options.fontname)
dot.attr('node',
shape='none',
width='0', height='0', margin='0', # Actual size of the node is entirely determined by the label.
style='filled',
fillcolor='white',
fontname=font)
fillcolor=wv_colors.translate_color(self.options.bgcolor_node, "HEX"),
fontname=self.options.fontname)
dot.attr('edge', style='bold',
fontname=font)
fontname=self.options.fontname)

# prepare ports on connectors depending on which side they will connect
for _, cable in self.cables.items():
Expand All @@ -126,7 +129,8 @@ def create_graph(self) -> Graph:
[html_line_breaks(connector.type),
html_line_breaks(connector.subtype),
f'{connector.pincount}-pin' if connector.show_pincount else None,
connector.color, html_colorbar(connector.color)],
translate_color(connector.color, self.options.color_mode) if connector.color else None,
html_colorbar(connector.color)],
'<!-- connector table -->' if connector.style != 'simple' else None,
[html_image(connector.image)],
[html_caption(connector.image)]]
Expand All @@ -148,7 +152,7 @@ def create_graph(self) -> Graph:
pinhtml.append(f' <td>{pinlabel}</td>')
if connector.pincolors:
if pincolor in wv_colors._color_hex.keys():
pinhtml.append(f' <td sides="tbl">{pincolor}</td>')
pinhtml.append(f' <td sides="tbl">{translate_color(pincolor, self.options.color_mode)}</td>')
pinhtml.append( ' <td sides="tbr">')
pinhtml.append( ' <table border="0" cellborder="1"><tr>')
pinhtml.append(f' <td bgcolor="{wv_colors.translate_color(pincolor, "HEX")}" width="8" height="8" fixedsize="true"></td>')
Expand All @@ -166,7 +170,8 @@ def create_graph(self) -> Graph:
html = [row.replace('<!-- connector table -->', '\n'.join(pinhtml)) for row in html]

html = '\n'.join(html)
dot.node(connector.name, label=f'<\n{html}\n>', shape='none', margin='0', style='filled', fillcolor='white')
dot.node(connector.name, label=f'<\n{html}\n>', shape='box', style='filled',
fillcolor=translate_color(self.options.bgcolor_connector, "HEX"))

if len(connector.loops) > 0:
dot.attr('edge', color='#000000:#ffffff:#000000')
Expand Down Expand Up @@ -211,7 +216,8 @@ def create_graph(self) -> Graph:
f'{cable.gauge} {cable.gauge_unit}{awg_fmt}' if cable.gauge else None,
'+ S' if cable.shield else None,
f'{cable.length} {cable.length_unit}' if cable.length > 0 else None,
cable.color, html_colorbar(cable.color)],
translate_color(cable.color, self.options.color_mode) if cable.color else None,
html_colorbar(cable.color)],
'<!-- wire table -->',
[html_image(cable.image)],
[html_caption(cable.image)]]
Expand All @@ -232,7 +238,7 @@ def create_graph(self) -> Graph:
wireinfo = []
if cable.show_wirenumbers:
wireinfo.append(str(i))
colorstr = wv_colors.translate_color(connection_color, self.color_mode)
colorstr = wv_colors.translate_color(connection_color, self.options.color_mode)
if colorstr:
wireinfo.append(colorstr)
if cable.wirelabels:
Expand Down Expand Up @@ -332,9 +338,11 @@ def create_graph(self) -> Graph:
to_string = ''
html = [row.replace(f'<!-- {connection.via_port}_out -->', to_string) for row in html]

style, bgcolor = ('filled,dashed', self.options.bgcolor_bundle) if cable.category == 'bundle' else \
('filled', self.options.bgcolor_cable)
html = '\n'.join(html)
dot.node(cable.name, label=f'<\n{html}\n>', shape='box',
style='filled,dashed' if cable.category == 'bundle' else '', margin='0', fillcolor='white')
style=style, fillcolor=translate_color(bgcolor, "HEX"))

return dot

Expand Down Expand Up @@ -368,7 +376,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True
with open_file_write(f'{filename}.bom.tsv') as file:
file.write(tuplelist2tsv(bomlist))
# HTML output
generate_html_output(filename, bomlist)
generate_html_output(filename, bomlist, self.metadata, self.options)

def bom(self):
if not self._bom:
Expand Down
8 changes: 7 additions & 1 deletion src/wireviz/wireviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from wireviz import __version__
from wireviz.DataClasses import Metadata, Options
from wireviz.Harness import Harness
from wireviz.wv_helper import expand, open_file_read

Expand All @@ -34,7 +35,12 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st

yaml_data = yaml.safe_load(yaml_input)

harness = Harness()
harness = Harness(
metadata = Metadata(**yaml_data.get('metadata', {})),
options = Options(**yaml_data.get('options', {})),
)
if 'title' not in harness.metadata:
harness.metadata['title'] = Path(file_out).stem

# add items
sections = ['connectors', 'cables', 'connections']
Expand Down
10 changes: 6 additions & 4 deletions src/wireviz/wv_bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any, Dict, List, Optional, Tuple, Union

from wireviz.DataClasses import AdditionalComponent, Connector, Cable
from wireviz.wv_colors import translate_color
from wireviz.wv_gv_html import html_line_breaks
from wireviz.wv_helper import clean_whitespace

Expand All @@ -32,7 +33,7 @@ def get_additional_component_table(harness: "Harness", component: Union[Connecto
'qty': part.qty * component.get_qty_multiplier(part.qty_multiplier),
'unit': part.unit,
}
if harness.mini_bom_mode:
if harness.options.mini_bom_mode:
id = get_bom_index(harness.bom(), bom_entry_key({**asdict(part), 'description': part.description}))
rows.append(component_table_entry(f'#{id} ({part.type.rstrip()})', **common_args))
else:
Expand Down Expand Up @@ -69,7 +70,7 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]:
+ (f', {connector.type}' if connector.type else '')
+ (f', {connector.subtype}' if connector.subtype else '')
+ (f', {connector.pincount} pins' if connector.show_pincount else '')
+ (f', {connector.color}' if connector.color else ''))
+ (f', {translate_color(connector.color, harness.options.color_mode)}' if connector.color else ''))
bom_entries.append({
'description': description, 'designators': connector.name if connector.show_name else None,
**optional_fields(connector),
Expand All @@ -88,7 +89,8 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]:
+ (f', {cable.type}' if cable.type else '')
+ (f', {cable.wirecount}')
+ (f' x {cable.gauge} {cable.gauge_unit}' if cable.gauge else ' wires')
+ (' shielded' if cable.shield else ''))
+ ( ' shielded' if cable.shield else '')
+ (f', {translate_color(cable.color, harness.options.color_mode)}' if cable.color else ''))
bom_entries.append({
'description': description, 'qty': cable.length, 'unit': cable.length_unit, 'designators': cable.name if cable.show_name else None,
**optional_fields(cable),
Expand All @@ -99,7 +101,7 @@ def generate_bom(harness: "Harness") -> List[BOMEntry]:
description = ('Wire'
+ (f', {cable.type}' if cable.type else '')
+ (f', {cable.gauge} {cable.gauge_unit}' if cable.gauge else '')
+ (f', {color}' if color else ''))
+ (f', {translate_color(color, harness.options.color_mode)}' if color else ''))
bom_entries.append({
'description': description, 'qty': cable.length, 'unit': cable.length_unit, 'designators': cable.name if cable.show_name else None,
**{k: index_if_list(v, index) for k, v in optional_fields(cable).items()},
Expand Down
43 changes: 27 additions & 16 deletions src/wireviz/wv_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@
# -*- coding: utf-8 -*-

from pathlib import Path
from typing import List, Union
import re

from wireviz import __version__, APP_NAME, APP_URL
from wireviz import __version__, APP_NAME, APP_URL, wv_colors
from wireviz.DataClasses import Metadata, Options
from wireviz.wv_helper import flatten2d, open_file_read, open_file_write

def generate_html_output(filename: (str, Path), bom_list):
def generate_html_output(filename: Union[str, Path], bom_list: List[List[str]], metadata: Metadata, options: Options):
with open_file_write(f'{filename}.html') as file:
file.write('<!DOCTYPE html>\n')
file.write('<html lang="en"><head>\n')
file.write(' <meta charset="UTF-8">\n')
file.write(f' <meta name="generator" content="{APP_NAME} {__version__} - {APP_URL}">\n')
file.write(f' <title>{APP_NAME} Diagram and BOM</title>\n')
file.write('</head><body style="font-family:Arial">\n')
file.write(f' <title>{metadata["title"]}</title>\n')
file.write(f'</head><body style="font-family:{options.fontname};background-color:'
f'{wv_colors.translate_color(options.bgcolor, "HEX")}">\n')

file.write('<h1>Diagram</h1>')
file.write(f'<h1>{metadata["title"]}</h1>\n')
description = metadata.get('description')
if description:
file.write(f'<p>{description}</p>\n')
file.write('<h2>Diagram</h2>\n')
with open_file_read(f'{filename}.svg') as svg:
file.write(re.sub(
'^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>',
Expand All @@ -25,20 +32,24 @@ def generate_html_output(filename: (str, Path), bom_list):
for svgdata in svg:
file.write(svgdata)

file.write('<h1>Bill of Materials</h1>')
file.write('<h2>Bill of Materials</h2>\n')
listy = flatten2d(bom_list)
file.write('<table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px">')
file.write('<tr>')
file.write('<table style="border:1px solid #000000; font-size: 14pt; border-spacing: 0px">\n')
file.write(' <tr>\n')
for item in listy[0]:
file.write(f'<th style="text-align:left; border:1px solid #000000; padding: 8px">{item}</th>')
file.write('</tr>')
file.write(f' <th style="text-align:left; border:1px solid #000000; padding: 8px">{item}</th>\n')
file.write(' </tr>\n')
for row in listy[1:]:
file.write('<tr>')
file.write(' <tr>\n')
for i, item in enumerate(row):
item_str = item.replace('\u00b2', '&sup2;')
align = 'text-align:right; ' if listy[0][i] == 'Qty' else ''
file.write(f'<td style="{align}border:1px solid #000000; padding: 4px">{item_str}</td>')
file.write('</tr>')
file.write('</table>')
align = '; text-align:right' if listy[0][i] == 'Qty' else ''
file.write(f' <td style="border:1px solid #000000; padding: 4px{align}">{item_str}</td>\n')
file.write(' </tr>\n')
file.write('</table>\n')

file.write('</body></html>')
notes = metadata.get('notes')
if notes:
file.write(f'<h2>Notes</h2>\n<p>{notes}</p>\n')

file.write('</body></html>\n')

0 comments on commit 92354e6

Please sign in to comment.