1import yaml2import re34from wireviz.wireviz import parse56WIREVIZ_VERSION = '0.3.2'7MAX_NODES = 20_0008MAX_DEPTH = 329MAX_RANGE = 4_09610MAX_COMPONENT_SIZE = 4_096111213def _preflight(value, depth=0, seen=None, count=None):14 seen = set() if seen is None else seen15 count = [0] if count is None else count16 if depth > MAX_DEPTH:17 raise ValueError('WireViz YAML nesting is too deep')18 count[0] += 119 if count[0] > MAX_NODES:20 raise ValueError('WireViz YAML document is too complex')21 if isinstance(value, (dict, list)):22 identity = id(value)23 if identity in seen:24 raise ValueError('recursive YAML aliases are not supported')25 seen.add(identity)26 values = value.values() if isinstance(value, dict) else value27 for item in values:28 _preflight(item, depth + 1, seen, count)29 seen.remove(identity)303132def _expanded_connection_size(value):33 if isinstance(value, dict):34 return sum(_expanded_connection_size(item) for item in value.values())35 if isinstance(value, list):36 return sum(_expanded_connection_size(item) for item in value)37 if isinstance(value, str):38 match = re.fullmatch(r'\s*(\d+)\s*-\s*(\d+)\s*', value)39 if match:40 size = abs(int(match[2]) - int(match[1])) + 141 if size > MAX_RANGE:42 raise ValueError('WireViz numeric ranges are too large')43 return size44 return 1454647def _bounded_component_size(attributes, section, name):48 if not isinstance(attributes, dict):49 raise ValueError(f"WireViz {section} '{name}' must be a mapping")50 count_fields = ('pincount', 'pins', 'pinlabels', 'pincolors') if section == 'connector' else ('wirecount', 'colors', 'wirelabels')51 for field in count_fields:52 value = attributes.get(field)53 size = len(value) if isinstance(value, list) else value if field.endswith('count') else None54 if size is not None and (not isinstance(size, int) or isinstance(size, bool) or not 0 <= size <= MAX_COMPONENT_SIZE):55 raise ValueError(f"WireViz {section} '{name}' {field} is too large")565758def render_dot(source: str) -> str:59 """Run the upstream WireViz parser/model, stopping before GraphViz."""60 document = yaml.safe_load(source)61 if not isinstance(document, dict):62 raise ValueError('WireViz document must be a mapping')63 _preflight(document)64 connectors = document.get('connectors') or {}65 cables = document.get('cables') or {}66 connections = document.get('connections') or []67 if not isinstance(connectors, dict) or not isinstance(cables, dict) or not isinstance(connections, list):68 raise ValueError('WireViz connectors and cables must be mappings and connections must be a list')69 if len(connectors) > 1024 or len(cables) > 1024:70 raise ValueError('WireViz has too many connectors or cables')71 if len(connections) > 4096 or _expanded_connection_size(connections) > MAX_NODES:72 raise ValueError('WireViz has too many connection sets')73 for name, attributes in connectors.items():74 _bounded_component_size(attributes, 'connector', name)75 for name, attributes in cables.items():76 _bounded_component_size(attributes, 'cable', name)77 if document.get('tweak'):78 raise ValueError('WireViz tweak directives are not supported by the edge translator')79 for section, values in (('connectors', connectors), ('cables', cables)):80 if isinstance(values, dict) and any(isinstance(value, dict) and value.get('image') for value in values.values()):81 raise ValueError('WireViz images are not supported by the edge translator')82 harness = parse(source, return_types='harness')83 if harness is None:84 raise ValueError('WireViz source did not produce a harness')85 return harness.create_graph().source