Add optional tweaking of the .gv output (#215)
Co-authored-by: Daniel Rojas <github@danielrojas.net>
This commit is contained in:
parent
92354e6852
commit
db05514469
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user