"""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 Any, Optional from engine.camera import Camera from engine.data_sources.sources import EmptyDataSource, HeadlinesDataSource from engine.display import DisplayRegistry from engine.effects import get_registry from engine.pipeline.adapters import ( CameraStage, DataSourceStage, DisplayStage, EffectPluginStage, FontStage, MessageOverlayStage, PositionStage, ) from engine.pipeline.adapters.positioning import PositioningMode from engine.pipeline.controller import Pipeline, PipelineConfig from engine.pipeline.core import PipelineContext from engine.pipeline.graph import Graph, NodeType from engine.pipeline.params import PipelineParams class GraphAdapter: """Converts Graph to Pipeline with existing Stage classes.""" def __init__(self, graph: Graph): self.graph = graph self.pipeline: Pipeline | None = None self.context: PipelineContext | None = 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)