Skip to content

Commit

Permalink
Add optional tweaking of the .gv output (#215)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Rojas <[email protected]>
  • Loading branch information
kvid and formatc1702 authored Sep 14, 2021
1 parent 92354e6 commit db05514
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 3 deletions.
28 changes: 28 additions & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -403,6 +430,7 @@ The following attributes accept multiline strings:
- `manufacturer`
- `mpn`
- `image.caption`
- `tweak.append`

### Method 1

Expand Down
6 changes: 6 additions & 0 deletions src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def __post_init__(self):
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
Expand Down
56 changes: 54 additions & 2 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,6 +25,7 @@
class Harness:
metadata: Metadata
options: Options
tweak: Tweak

def __post_init__(self):
self.connectors = {}
Expand Down Expand Up @@ -344,6 +345,57 @@ 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, 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
Expand Down
3 changes: 2 additions & 1 deletion src/wireviz/wireviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down

0 comments on commit db05514

Please sign in to comment.