forked from genewildish/Mainline
docs(graph): Add DSL documentation and examples
Add comprehensive documentation for the graph-based pipeline DSL: - docs/graph-dsl.md: Complete DSL reference with TOML, Python, and CLI syntax - docs/GRAPH_SYSTEM_SUMMARY.md: Implementation overview and architecture - examples/graph_dsl_demo.py: Demonstrates imperative Python API usage - examples/test_graph_integration.py: Integration test verifying pipeline execution The documentation follows a wiki-like approach with navigable structure: - Overview section explaining the concept - Syntax examples for each format (TOML, Python, CLI) - Node type reference table - Advanced features section - Comparison with old XYZStage approach This provides users with multiple entry points to understand and use the new graph-based pipeline system.
This commit is contained in:
136
examples/graph_dsl_demo.py
Normal file
136
examples/graph_dsl_demo.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Demo script showing the new graph-based DSL for pipeline configuration.
|
||||
|
||||
This demonstrates how to define pipelines using the graph abstraction,
|
||||
which is more intuitive than the verbose XYZStage naming convention.
|
||||
"""
|
||||
|
||||
from engine.effects.plugins import discover_plugins
|
||||
from engine.pipeline.graph import Graph, NodeType
|
||||
from engine.pipeline.graph_adapter import graph_to_pipeline, dict_to_pipeline
|
||||
|
||||
|
||||
def demo_imperative_api():
|
||||
"""Demo: Imperative Python API for building graphs."""
|
||||
print("=== Imperative Python API ===")
|
||||
|
||||
graph = Graph()
|
||||
graph.node("source", NodeType.SOURCE, source="headlines")
|
||||
graph.node("camera", NodeType.CAMERA, mode="scroll", speed=1.0)
|
||||
graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.3)
|
||||
graph.node("display", NodeType.DISPLAY, backend="null")
|
||||
|
||||
# Connect nodes in a chain
|
||||
graph.chain("source", "camera", "noise", "display")
|
||||
|
||||
# Validate the graph
|
||||
errors = graph.validate()
|
||||
if errors:
|
||||
print(f"Validation errors: {errors}")
|
||||
return
|
||||
|
||||
# Convert to pipeline
|
||||
pipeline = graph_to_pipeline(graph, viewport_width=80, viewport_height=24)
|
||||
|
||||
print(f"Pipeline created with {len(pipeline._stages)} stages:")
|
||||
for name, stage in pipeline._stages.items():
|
||||
print(f" - {name}: {stage.__class__.__name__}")
|
||||
|
||||
return pipeline
|
||||
|
||||
|
||||
def demo_dict_api():
|
||||
"""Demo: Dictionary-based API for building graphs."""
|
||||
print("\n=== Dictionary API ===")
|
||||
|
||||
data = {
|
||||
"nodes": {
|
||||
"source": "headlines",
|
||||
"camera": {"type": "camera", "mode": "scroll", "speed": 1.0},
|
||||
"noise": {"type": "effect", "effect": "noise", "intensity": 0.5},
|
||||
"fade": {"type": "effect", "effect": "fade", "intensity": 0.8},
|
||||
"display": {"type": "display", "backend": "null"},
|
||||
},
|
||||
"connections": ["source -> camera -> noise -> fade -> display"],
|
||||
}
|
||||
|
||||
pipeline = dict_to_pipeline(data, viewport_width=80, viewport_height=24)
|
||||
|
||||
print(f"Pipeline created with {len(pipeline._stages)} stages:")
|
||||
for name, stage in pipeline._stages.items():
|
||||
print(f" - {name}: {stage.__class__.__name__}")
|
||||
|
||||
return pipeline
|
||||
|
||||
|
||||
def demo_graph_validation():
|
||||
"""Demo: Graph validation."""
|
||||
print("\n=== Graph Validation ===")
|
||||
|
||||
# Create a graph with a cycle
|
||||
graph = Graph()
|
||||
graph.node("a", NodeType.SOURCE)
|
||||
graph.node("b", NodeType.CAMERA)
|
||||
graph.node("c", NodeType.DISPLAY)
|
||||
graph.connect("a", "b")
|
||||
graph.connect("b", "c")
|
||||
graph.connect("c", "a") # Creates cycle
|
||||
|
||||
errors = graph.validate()
|
||||
print(f"Cycle detection errors: {errors}")
|
||||
|
||||
# Create a valid graph
|
||||
graph2 = Graph()
|
||||
graph2.node("source", NodeType.SOURCE, source="headlines")
|
||||
graph2.node("display", NodeType.DISPLAY, backend="null")
|
||||
graph2.connect("source", "display")
|
||||
|
||||
errors2 = graph2.validate()
|
||||
print(f"Valid graph errors: {errors2}")
|
||||
|
||||
|
||||
def demo_node_types():
|
||||
"""Demo: Different node types."""
|
||||
print("\n=== Node Types ===")
|
||||
|
||||
graph = Graph()
|
||||
|
||||
# Source node
|
||||
graph.node("headlines", NodeType.SOURCE, source="headlines")
|
||||
print("✓ Source node created")
|
||||
|
||||
# Camera node with different modes
|
||||
graph.node("camera_scroll", NodeType.CAMERA, mode="scroll", speed=1.0)
|
||||
graph.node("camera_feed", NodeType.CAMERA, mode="feed", speed=0.5)
|
||||
graph.node("camera_horizontal", NodeType.CAMERA, mode="horizontal", speed=1.0)
|
||||
print("✓ Camera nodes created (scroll, feed, horizontal)")
|
||||
|
||||
# Effect nodes
|
||||
graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.3)
|
||||
graph.node("fade", NodeType.EFFECT, effect="fade", intensity=0.8)
|
||||
print("✓ Effect nodes created (noise, fade)")
|
||||
|
||||
# Positioning node
|
||||
graph.node("position", NodeType.POSITION, mode="mixed")
|
||||
print("✓ Positioning node created")
|
||||
|
||||
# Display nodes
|
||||
graph.node("terminal", NodeType.DISPLAY, backend="terminal")
|
||||
graph.node("null", NodeType.DISPLAY, backend="null")
|
||||
print("✓ Display nodes created")
|
||||
|
||||
print(f"\nTotal nodes: {len(graph.nodes)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Discover effect plugins first
|
||||
discover_plugins()
|
||||
|
||||
# Run demos
|
||||
demo_imperative_api()
|
||||
demo_dict_api()
|
||||
demo_graph_validation()
|
||||
demo_node_types()
|
||||
|
||||
print("\n=== Demo Complete ===")
|
||||
110
examples/test_graph_integration.py
Normal file
110
examples/test_graph_integration.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify graph-based pipeline integration.
|
||||
|
||||
This script tests that the graph DSL can be used to create working pipelines
|
||||
that produce output similar to preset-based pipelines.
|
||||
"""
|
||||
|
||||
from engine.effects.plugins import discover_plugins
|
||||
from engine.pipeline.graph_toml import load_pipeline_from_toml
|
||||
from engine.pipeline.params import PipelineParams
|
||||
|
||||
|
||||
def test_graph_pipeline_execution():
|
||||
"""Test that a graph-based pipeline can execute and produce output."""
|
||||
print("=== Testing Graph Pipeline Execution ===")
|
||||
|
||||
# Discover plugins
|
||||
discover_plugins()
|
||||
|
||||
# Load pipeline from TOML
|
||||
pipeline = load_pipeline_from_toml(
|
||||
"examples/pipeline_graph.toml", viewport_width=80, viewport_height=24
|
||||
)
|
||||
|
||||
print(f"Pipeline loaded with {len(pipeline._stages)} stages")
|
||||
print(f"Stages: {list(pipeline._stages.keys())}")
|
||||
|
||||
# Initialize pipeline
|
||||
if not pipeline.initialize():
|
||||
print("Failed to initialize pipeline")
|
||||
return False
|
||||
|
||||
print("Pipeline initialized successfully")
|
||||
|
||||
# Set up context
|
||||
ctx = pipeline.context
|
||||
params = PipelineParams(viewport_width=80, viewport_height=24)
|
||||
ctx.params = params
|
||||
|
||||
# Execute pipeline with empty items (source will provide content)
|
||||
result = pipeline.execute([])
|
||||
|
||||
if result.success:
|
||||
print(f"Pipeline executed successfully")
|
||||
print(f"Output type: {type(result.data)}")
|
||||
if isinstance(result.data, list):
|
||||
print(f"Output lines: {len(result.data)}")
|
||||
if len(result.data) > 0:
|
||||
print(f"First line: {result.data[0][:50]}...")
|
||||
return True
|
||||
else:
|
||||
print(f"Pipeline execution failed: {result.error}")
|
||||
return False
|
||||
|
||||
|
||||
def test_graph_vs_preset():
|
||||
"""Compare graph-based and preset-based pipelines."""
|
||||
print("\n=== Comparing Graph vs Preset ===")
|
||||
|
||||
from engine.pipeline import get_preset
|
||||
|
||||
# Load graph-based pipeline
|
||||
graph_pipeline = load_pipeline_from_toml(
|
||||
"examples/pipeline_graph.toml", viewport_width=80, viewport_height=24
|
||||
)
|
||||
|
||||
# Load preset-based pipeline (using test-basic as a base)
|
||||
preset = get_preset("test-basic")
|
||||
if not preset:
|
||||
print("test-basic preset not found")
|
||||
return False
|
||||
|
||||
# Create pipeline from preset config
|
||||
from engine.pipeline import Pipeline
|
||||
|
||||
preset_pipeline = Pipeline(config=preset.to_config())
|
||||
|
||||
print(f"Graph pipeline stages: {len(graph_pipeline._stages)}")
|
||||
print(f"Preset pipeline stages: {len(preset_pipeline._stages)}")
|
||||
|
||||
# Compare stage types
|
||||
graph_stage_types = {
|
||||
name: stage.__class__.__name__ for name, stage in graph_pipeline._stages.items()
|
||||
}
|
||||
preset_stage_types = {
|
||||
name: stage.__class__.__name__
|
||||
for name, stage in preset_pipeline._stages.items()
|
||||
}
|
||||
|
||||
print("\nGraph pipeline stages:")
|
||||
for name, stage_type in graph_stage_types.items():
|
||||
print(f" - {name}: {stage_type}")
|
||||
|
||||
print("\nPreset pipeline stages:")
|
||||
for name, stage_type in preset_stage_types.items():
|
||||
print(f" - {name}: {stage_type}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success1 = test_graph_pipeline_execution()
|
||||
success2 = test_graph_vs_preset()
|
||||
|
||||
if success1 and success2:
|
||||
print("\n✓ All tests passed!")
|
||||
else:
|
||||
print("\n✗ Some tests failed")
|
||||
exit(1)
|
||||
Reference in New Issue
Block a user