From 723235759b760cba778509136ac3c2fa82c7e15a Mon Sep 17 00:00:00 2001 From: KV Date: Thu, 11 Feb 2021 19:25:41 +0100 Subject: [PATCH] Add optional tweaking of the .gv output Solves #174 intermediately by allowing low level .gv tweaking. --- docs/syntax.md | 27 +++++++++++++++++++++++++ src/wireviz/DataClasses.py | 6 ++++++ src/wireviz/Harness.py | 41 +++++++++++++++++++++++++++++++++++++- src/wireviz/wireviz.py | 3 ++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index 4a7a52ac..7dbc6f86 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -35,6 +35,8 @@ additional_bom_items: # custom items to add to BOM - # BOM item (see below) ... +tweak: # optional tweaking of .gv output + ... ``` ## Metadata entries @@ -327,6 +329,30 @@ Alternatively items can be added to just the BOM by putting them in the section manufacturer: # manufacturer name ``` +## Tweak entries + +```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 containing HTML in an attribute are + # not supported. + : # leading string of .gv entry + : # attribute and its new value + # Any number of attributes can be overridden + # for each entry + + append: # single or list of .gv entries to append + # strings to append might have multiple lines +``` + ## Colors Colors are defined via uppercase, two character strings. @@ -403,6 +429,7 @@ The following attributes accept multiline strings: - `manufacturer` - `mpn` - `image.caption` +- `tweak.append` ### Method 1 diff --git a/src/wireviz/DataClasses.py b/src/wireviz/DataClasses.py index e80437b2..c25b2489 100644 --- a/src/wireviz/DataClasses.py +++ b/src/wireviz/DataClasses.py @@ -59,6 +59,12 @@ def __post_init__(self): self.bgcolor_bundle = self.bgcolor_cable +@dataclass +class Tweak: + override: Optional[Dict[Designator, Dict[str, 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 diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 39180500..1d6c586f 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -10,7 +10,7 @@ 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 @@ class Harness: metadata: Metadata options: Options + tweak: Tweak def __post_init__(self): self.connectors = {} @@ -344,6 +345,44 @@ def create_graph(self) -> Graph: dot.node(cable.name, label=f'<\n{html}\n>', shape='box', style=style, fillcolor=translate_color(bgcolor, "HEX")) + def typecheck(name: str, var, type) -> None: + if not isinstance(var, type): + raise Exception(f'Unexpected value type of {name}: {var}') + + # 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) + + # 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 len(value) == 0 or ' ' in value: + value = value.replace('"', r'\"') + value = f'"{value}"' + # TODO?: If value is None: delete attr, and if attr not found: append it? + entry = re.sub(f'{attr}=("[^"]*"|[^] ]*)', f'{attr}={value}', entry) + 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(f'tweak.append', self.tweak.append, str) + dot.body.append(self.tweak.append) + return dot @property diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 6ba77eec..95674945 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -13,7 +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.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