"""template — One-Shot YAML Template Generator from AST nodes.""" from .grammar import Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty def format_cardinality(node): """Return cardinality description for an AST node.""" if isinstance(node, Plus): return '# PFLICHT: 1 oder mehrmals erforderlich' if isinstance(node, Optional): return '# OPTIONAL: 0 oder 1 mal (darf weggelassen werden)' if isinstance(node, Star): return '# OPTIONAL: 0 oder mehrmals' return '# PFLICHT: Genau 1 mal erforderlich' def generate_template(node, context_key=None, include_header=True): """Generate a YAML one-shot template from an AST node. Args: node: Grammar AST node context_key: YAML container key (e.g. 'tasks') include_header: Whether to include header section (name, hosts) Returns: YAML skeleton with placeholders and cardinality comments """ if node is None or isinstance(node, Empty): return "# No structure inferred (empty sequences or no examples)" if include_header: lines = [ "- name: ", " hosts: # PFLICHT: Genau 1 mal erforderlich", ] if context_key: lines.append(f" {context_key}:") else: lines.append(" tasks:") indent = " " else: lines = [] if context_key: lines.append(f" {context_key}: # Container-Kontext: {context_key}") else: lines.append(" tasks:") indent = " " _generate_node(node, lines, indent) return '\n'.join(lines) + '\n' def _generate_node(node, lines, indent): """Recursively generate YAML lines from AST node.""" if isinstance(node, Symbol): lines.append(f"{indent}- {node.value}: ") elif isinstance(node, Plus): card = format_cardinality(node) _generate_wrapped(node.child, lines, indent, card) elif isinstance(node, Optional): card = format_cardinality(node) _generate_wrapped(node.child, lines, indent, card) elif isinstance(node, Star): card = format_cardinality(node) _generate_wrapped(node.child, lines, indent, card) elif isinstance(node, Alt): lines.append(f"{indent}# CHOOSE (pick one):") for part in node.parts: name = _node_name(part) lines.append(f"{indent}# - {name}: ") elif isinstance(node, Concat): for part in node.parts: _generate_node(part, lines, indent) elif isinstance(node, Epsilon): pass elif isinstance(node, Empty): pass def _generate_wrapped(child, lines, indent, card): """Generate a child node with cardinality annotation.""" if isinstance(child, Alt): lines.append(f"{indent}# CHOOSE (pick one):") for part in child.parts: name = _node_name(part) lines.append(f"{indent}# - {name}: ") lines[-1] = f"{lines[-1]} {card}" elif isinstance(child, Symbol): lines.append(f"{indent}- {child.value}: {card}") elif isinstance(child, Concat): for part in child.parts: _generate_node(part, lines, indent) else: _generate_node(child, lines, indent) def _node_name(node): """Get a human-readable name for a node.""" if isinstance(node, Symbol): return node.value if isinstance(node, Concat): return ".".join(_node_name(p) for p in node.parts) if isinstance(node, Alt): return "|".join(_node_name(p) for p in node.parts) return "..."