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)
|
- <bom-item> # BOM item (see below)
|
||||||
...
|
...
|
||||||
|
|
||||||
|
tweak: # optional tweaking of .gv output
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Metadata entries
|
## 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
|
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
|
||||||
|
|
||||||
Colors are defined via uppercase, two character strings.
|
Colors are defined via uppercase, two character strings.
|
||||||
@ -403,6 +430,7 @@ The following attributes accept multiline strings:
|
|||||||
- `manufacturer`
|
- `manufacturer`
|
||||||
- `mpn`
|
- `mpn`
|
||||||
- `image.caption`
|
- `image.caption`
|
||||||
|
- `tweak.append`
|
||||||
|
|
||||||
### Method 1
|
### Method 1
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,12 @@ class Options:
|
|||||||
self.bgcolor_bundle = self.bgcolor_cable
|
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
|
@dataclass
|
||||||
class Image:
|
class Image:
|
||||||
gv_dir: InitVar[Path] # Directory of .gv file injected as context during parsing
|
gv_dir: InitVar[Path] # Directory of .gv file injected as context during parsing
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
from graphviz import Graph
|
from graphviz import Graph
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Union
|
from typing import Any, List, Union
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from wireviz import wv_colors, __version__, APP_NAME, APP_URL
|
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_colors import get_color_hex, translate_color
|
||||||
from wireviz.wv_gv_html import nested_html_table, html_colorbar, html_image, \
|
from wireviz.wv_gv_html import nested_html_table, html_colorbar, html_image, \
|
||||||
html_caption, remove_links, html_line_breaks
|
html_caption, remove_links, html_line_breaks
|
||||||
@ -25,6 +25,7 @@ from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, flatten2d, \
|
|||||||
class Harness:
|
class Harness:
|
||||||
metadata: Metadata
|
metadata: Metadata
|
||||||
options: Options
|
options: Options
|
||||||
|
tweak: Tweak
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.connectors = {}
|
self.connectors = {}
|
||||||
@ -344,6 +345,57 @@ class Harness:
|
|||||||
dot.node(cable.name, label=f'<\n{html}\n>', shape='box',
|
dot.node(cable.name, label=f'<\n{html}\n>', shape='box',
|
||||||
style=style, fillcolor=translate_color(bgcolor, "HEX"))
|
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
|
return dot
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -13,7 +13,7 @@ if __name__ == '__main__':
|
|||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
from wireviz import __version__
|
from wireviz import __version__
|
||||||
from wireviz.DataClasses import Metadata, Options
|
from wireviz.DataClasses import Metadata, Options, Tweak
|
||||||
from wireviz.Harness import Harness
|
from wireviz.Harness import Harness
|
||||||
from wireviz.wv_helper import expand, open_file_read
|
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(
|
harness = Harness(
|
||||||
metadata = Metadata(**yaml_data.get('metadata', {})),
|
metadata = Metadata(**yaml_data.get('metadata', {})),
|
||||||
options = Options(**yaml_data.get('options', {})),
|
options = Options(**yaml_data.get('options', {})),
|
||||||
|
tweak = Tweak(**yaml_data.get('tweak', {})),
|
||||||
)
|
)
|
||||||
if 'title' not in harness.metadata:
|
if 'title' not in harness.metadata:
|
||||||
harness.metadata['title'] = Path(file_out).stem
|
harness.metadata['title'] = Path(file_out).stem
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user