From 406a58d2920c8cae73a88c73631d8d2b50d8591b Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Sat, 21 Mar 2026 19:26:39 -0700 Subject: [PATCH] feat(graph): Add TOML-based graph configuration loader Allow pipelines to be defined in TOML format with intuitive node-and-connection syntax that's easy to read and edit. - Add graph_toml.py with TOML parsing using tomllib - Support simple format: "source": "headlines" - Support full format: {"type": "camera", "mode": "scroll"} - Parse connection strings in "A -> B -> C" chain format - Add example pipeline_graph.toml demonstrating usage Example TOML: [nodes.source] type = "source" source = "headlines" [nodes.camera] type = "camera" mode = "scroll" [connections] list = ["source -> camera -> display"] --- engine/pipeline/graph_toml.py | 112 ++++++++++++++++++++++++++++++++++ examples/pipeline_graph.toml | 28 +++++++++ 2 files changed, 140 insertions(+) create mode 100644 engine/pipeline/graph_toml.py create mode 100644 examples/pipeline_graph.toml diff --git a/engine/pipeline/graph_toml.py b/engine/pipeline/graph_toml.py new file mode 100644 index 0000000..aa88726 --- /dev/null +++ b/engine/pipeline/graph_toml.py @@ -0,0 +1,112 @@ +"""TOML-based graph configuration loader.""" + +import tomllib +from pathlib import Path +from typing import Any, Dict + +from engine.pipeline.graph import Graph, NodeType +from engine.pipeline.graph_adapter import graph_to_pipeline + + +def load_graph_from_toml(toml_path: str | Path) -> Graph: + """Load a graph from a TOML file. + + Args: + toml_path: Path to the TOML file + + Returns: + Graph instance loaded from the TOML file + """ + with open(toml_path, "rb") as f: + data = tomllib.load(f) + + return graph_from_dict(data) + + +def graph_from_dict(data: Dict[str, Any]) -> Graph: + """Create a graph from a dictionary (TOML-compatible structure). + + Args: + data: Dictionary with 'nodes' and 'connections' keys + + Returns: + Graph instance + """ + graph = Graph() + + # Parse nodes + nodes_data = data.get("nodes", {}) + for name, node_info in nodes_data.items(): + if isinstance(node_info, str): + # Simple format: "source": "headlines" + graph.node(name, NodeType.SOURCE, source=node_info) + elif isinstance(node_info, dict): + # Full format: {"type": "camera", "mode": "scroll"} + node_type = node_info.get("type", "custom") + config = {k: v for k, v in node_info.items() if k != "type"} + graph.node(name, node_type, **config) + + # Parse connections + connections_data = data.get("connections", {}) + if isinstance(connections_data, dict): + # Format: {"list": ["source -> camera -> display"]} + connections_list = connections_data.get("list", []) + else: + # Format: ["source -> camera -> display"] + connections_list = connections_data + + for conn in connections_list: + if isinstance(conn, str): + # Parse "source -> target" format + parts = conn.split("->") + if len(parts) >= 2: + # Connect all nodes in the chain + for i in range(len(parts) - 1): + source = parts[i].strip() + target = parts[i + 1].strip() + graph.connect(source, target) + + return graph + + +def load_pipeline_from_toml( + toml_path: str | Path, viewport_width: int = 80, viewport_height: int = 24 +): + """Load a pipeline from a TOML file. + + Args: + toml_path: Path to the TOML file + viewport_width: Terminal width for the pipeline + viewport_height: Terminal height for the pipeline + + Returns: + Pipeline instance loaded from the TOML file + """ + graph = load_graph_from_toml(toml_path) + return graph_to_pipeline(graph, viewport_width, viewport_height) + + +# Example TOML structure: +EXAMPLE_TOML = """ +# Graph-based pipeline configuration +[nodes.source] +type = "source" +source = "headlines" + +[nodes.camera] +type = "camera" +mode = "scroll" +speed = 1.0 + +[nodes.noise] +type = "effect" +effect = "noise" +intensity = 0.3 + +[nodes.display] +type = "display" +backend = "terminal" + +[connections] +list = ["source -> camera -> noise -> display"] +""" diff --git a/examples/pipeline_graph.toml b/examples/pipeline_graph.toml new file mode 100644 index 0000000..9eff0ec --- /dev/null +++ b/examples/pipeline_graph.toml @@ -0,0 +1,28 @@ +# Graph-based pipeline configuration example +# This defines a pipeline using the new graph DSL + +[nodes.source] +type = "source" +source = "headlines" + +[nodes.camera] +type = "camera" +mode = "scroll" +speed = 1.0 + +[nodes.noise] +type = "effect" +effect = "noise" +intensity = 0.3 + +[nodes.fade] +type = "effect" +effect = "fade" +intensity = 0.8 + +[nodes.display] +type = "display" +backend = "null" + +[connections] +list = ["source -> camera -> noise -> fade -> display"]