Add optional tweaking of the .gv output (#215)

Co-authored-by: Daniel Rojas <github@danielrojas.net>
This commit is contained in:
kvid 2021-09-14 19:20:51 +02:00 committed by GitHub
parent 92354e6852
commit db05514469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 3 deletions

View File

@ -35,6 +35,8 @@ additional_bom_items: # custom items to add to BOM
- <bom-item> # BOM item (see below)
...
tweak: # optional tweaking of .gv output
...
```
## Metadata entries
@ -327,6 +329,31 @@ Alternatively items can be added to just the BOM by putting them in the section
manufacturer: <str> # manufacturer name
```
## GraphViz tweaking (experimental)
```yaml
# Optional tweaking of the .gv output.
# This feature is experimental and might change
# or be removed in future versions.
override: # dict of .gv entries to override
# Each entry is identified by its leading string
# in lines beginning with a TAB character.
# The leading string might be in "quotes" in
# the .gv output. This leading string must be
# followed by attributes in [square brackets].
# Entries with an attribute containing HTML are
# not supported.
<str>: # leading string of .gv entry
<str> : <str/null> # attribute and its new value
# Any number of attributes can be overridden
# for each entry. Attributes not already existing
# in the entry will be appended to the entry.
# Use null as new value to delete an attribute.
append: <str/list> # string or list of strings to append to the .gv output
```
## Colors
Colors are defined via uppercase, two character strings.
@ -403,6 +430,7 @@ The following attributes accept multiline strings:
- `manufacturer`
- `mpn`
- `image.caption`
- `tweak.append`
### Method 1

View File

@ -59,6 +59,12 @@ class Options:
self.bgcolor_bundle = self.bgcolor_cable
@dataclass
class Tweak:
override: Optional[Dict[Designator, Dict[str, Optional[str]]]] = None
append: Union[str, List[str], None] = None
@dataclass
class Image:
gv_dir: InitVar[Path] # Directory of .gv file injected as context during parsing

View File

@ -3,14 +3,14 @@
from graphviz import Graph
from collections import Counter
from typing import List, Union
from typing import Any, 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 Metadata, Options, Connector, Cable
from wireviz.DataClasses import Metadata, Options, Tweak, 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
@ -25,6 +25,7 @@ from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, flatten2d, \
class Harness:
metadata: Metadata
options: Options
tweak: Tweak
def __post_init__(self):
self.connectors = {}
@ -344,6 +345,57 @@ class Harness:
dot.node(cable.name, label=f'<\n{html}\n>', shape='box',
style=style, fillcolor=translate_color(bgcolor, "HEX"))
def typecheck(name: str, value: Any, expect: type) -> None:
if not isinstance(value, expect):
raise Exception(f'Unexpected value type of {name}: Expected {expect}, got {type(value)}\n{value}')
# TODO?: Differ between override attributes and HTML?
if self.tweak.override is not None:
typecheck('tweak.override', self.tweak.override, dict)
for k, d in self.tweak.override.items():
typecheck(f'tweak.override.{k} key', k, str)
typecheck(f'tweak.override.{k} value', d, dict)
for a, v in d.items():
typecheck(f'tweak.override.{k}.{a} key', a, str)
typecheck(f'tweak.override.{k}.{a} value', v, (str, type(None)))
# Override generated attributes of selected entries matching tweak.override.
for i, entry in enumerate(dot.body):
if isinstance(entry, str):
# Find a possibly quoted keyword after leading TAB(s) and followed by [ ].
match = re.match(r'^\t*(")?((?(1)[^"]|[^ "])+)(?(1)") \[.*\]$', entry, re.S)
keyword = match and match[2]
if keyword in self.tweak.override.keys():
for attr, value in self.tweak.override[keyword].items():
if value is None:
entry, n_subs = re.subn(f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', '', entry)
if n_subs < 1:
print(f'Harness.create_graph() warning: {attr} not found in {keyword}!')
elif n_subs > 1:
print(f'Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!')
continue
if len(value) == 0 or ' ' in value:
value = value.replace('"', r'\"')
value = f'"{value}"'
entry, n_subs = re.subn(f'{attr}=("[^"]*"|[^] ]*)', f'{attr}={value}', entry)
if n_subs < 1:
# If attr not found, then append it
entry = re.sub(r'\]$', f' {attr}={value}]', entry)
elif n_subs > 1:
print(f'Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!')
dot.body[i] = entry
if self.tweak.append is not None:
if isinstance(self.tweak.append, list):
for i, element in enumerate(self.tweak.append, 1):
typecheck(f'tweak.append[{i}]', element, str)
dot.body.extend(self.tweak.append)
else:
typecheck('tweak.append', self.tweak.append, str)
dot.body.append(self.tweak.append)
return dot
@property

View File

@ -13,7 +13,7 @@ if __name__ == '__main__':
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from wireviz import __version__
from wireviz.DataClasses import Metadata, Options
from wireviz.DataClasses import Metadata, Options, Tweak
from wireviz.Harness import Harness
from wireviz.wv_helper import expand, open_file_read
@ -38,6 +38,7 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st
harness = Harness(
metadata = Metadata(**yaml_data.get('metadata', {})),
options = Options(**yaml_data.get('options', {})),
tweak = Tweak(**yaml_data.get('tweak', {})),
)
if 'title' not in harness.metadata:
harness.metadata['title'] = Path(file_out).stem