"""Adapter to convert Graph to Pipeline stages. This module bridges the new graph-based abstraction with the existing Stage-based pipeline system for backward compatibility. """ from typing import Dict, Any, List, Optional from engine.pipeline.graph import Graph, NodeType from engine.pipeline.controller import Pipeline, PipelineConfig from engine.pipeline.core import PipelineContext from engine.pipeline.params import PipelineParams from engine.pipeline.adapters import ( CameraStage, DataSourceStage, DisplayStage, EffectPluginStage, FontStage, MessageOverlayStage, PositionStage, ViewportFilterStage, create_stage_from_display, create_stage_from_effect, ) from engine.pipeline.adapters.positioning import PositioningMode from engine.display import DisplayRegistry from engine.effects import get_registry from engine.data_sources.sources import EmptyDataSource, HeadlinesDataSource from engine.camera import Camera class GraphAdapter: """Converts Graph to Pipeline with existing Stage classes.""" def __init__(self, graph: Graph): self.graph = graph self.pipeline: Optional[Pipeline] = None self.context: Optional[PipelineContext] = None def build_pipeline( self, viewport_width: int = 80, viewport_height: int = 24 ) -> Pipeline: """Build a Pipeline from the Graph.""" # Create pipeline context self.context = PipelineContext() self.context.terminal_width = viewport_width self.context.terminal_height = viewport_height # Create params params = PipelineParams( viewport_width=viewport_width, viewport_height=viewport_height, ) self.context.params = params # Create pipeline config config = PipelineConfig() # Create pipeline self.pipeline = Pipeline(config=config, context=self.context) # Map graph nodes to pipeline stages self._map_nodes_to_stages() # Build pipeline self.pipeline.build() return self.pipeline def _map_nodes_to_stages(self) -> None: """Map graph nodes to pipeline stages.""" for name, node in self.graph.nodes.items(): if not node.enabled: continue stage = self._create_stage_from_node(name, node) if stage: self.pipeline.add_stage(name, stage) def _create_stage_from_node(self, name: str, node) -> Optional: """Create a pipeline stage from a graph node.""" stage = None if node.type == NodeType.SOURCE: source_type = node.config.get("source", "headlines") if source_type == "headlines": source = HeadlinesDataSource() elif source_type == "empty": source = EmptyDataSource( width=self.context.terminal_width, height=self.context.terminal_height, ) else: source = EmptyDataSource( width=self.context.terminal_width, height=self.context.terminal_height, ) stage = DataSourceStage(source, name=name) elif node.type == NodeType.CAMERA: mode = node.config.get("mode", "scroll") speed = node.config.get("speed", 1.0) # Map mode string to Camera factory method mode_lower = mode.lower() if hasattr(Camera, mode_lower): camera_factory = getattr(Camera, mode_lower) camera = camera_factory(speed=speed) else: # Fallback to scroll mode camera = Camera.scroll(speed=speed) stage = CameraStage(camera, name=name) elif node.type == NodeType.DISPLAY: backend = node.config.get("backend", "terminal") positioning = node.config.get("positioning", "mixed") display = DisplayRegistry.create(backend) if display: stage = DisplayStage(display, name=name, positioning=positioning) elif node.type == NodeType.EFFECT: effect_name = node.config.get("effect", "") intensity = node.config.get("intensity", 1.0) effect = get_registry().get(effect_name) if effect: # Set effect intensity (modifies global effect state) effect.config.intensity = intensity # Effects typically depend on rendered output dependencies = {"render.output"} stage = EffectPluginStage(effect, name=name, dependencies=dependencies) elif node.type == NodeType.RENDER: stage = FontStage(name=name) elif node.type == NodeType.OVERLAY: stage = MessageOverlayStage(name=name) elif node.type == NodeType.POSITION: mode_str = node.config.get("mode", "mixed") try: mode = PositioningMode(mode_str) except ValueError: mode = PositioningMode.MIXED stage = PositionStage(mode=mode, name=name) return stage def graph_to_pipeline( graph: Graph, viewport_width: int = 80, viewport_height: int = 24 ) -> Pipeline: """Convert a Graph to a Pipeline.""" adapter = GraphAdapter(graph) return adapter.build_pipeline(viewport_width, viewport_height) def dict_to_pipeline( data: Dict[str, Any], viewport_width: int = 80, viewport_height: int = 24 ) -> Pipeline: """Convert a dictionary to a Pipeline.""" graph = Graph().from_dict(data) return graph_to_pipeline(graph, viewport_width, viewport_height)