grammar-inference-engine/bex/template.py

105 lines
3.6 KiB
Python

"""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: <Name des Plays>",
" hosts: <Ziel-Server> # 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}: <params>")
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}: <params>")
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}: <params>")
lines[-1] = f"{lines[-1]} {card}"
elif isinstance(child, Symbol):
lines.append(f"{indent}- {child.value}: <params> {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 "..."