Source code for routilux.builtin_routines.text_processing.text_renderer

"""
Text renderer routine.

Renders objects (dicts, lists) into formatted text with XML-like tags.
"""

from __future__ import annotations

from typing import Any

from routilux.routine import Routine


[docs] class TextRenderer(Routine): """Routine for rendering objects into formatted text. This routine converts dictionaries and lists into formatted text with XML-like tags, making structured data more readable. Features: - Recursively renders nested dictionaries and lists - Adds XML-like tags for structure - Handles primitive types (str, int, float, bool) - Configurable tag formatting Examples: >>> renderer = TextRenderer() >>> renderer.define_slot("input", handler=renderer.render) >>> renderer.define_event("output", ["rendered_text"]) >>> # Render a dictionary >>> data = {"name": "test", "value": 42} >>> # Output: "<name>test</name>\\n<value>42</value>" """
[docs] def __init__(self): """Initialize TextRenderer routine.""" super().__init__() # Set default configuration self.set_config( tag_format="xml", # "xml" or "markdown" indent=" ", # Indentation for nested structures include_type_hints=False, ) # Define input slot self.input_slot = self.define_slot("input", handler=self._handle_input) # Define output event self.output_event = self.define_event("output", ["rendered_text", "original_type"])
def _handle_input(self, data: Any = None, **kwargs): """Handle input data and render it. Args: data: Data to render (dict, list, or primitive type). Can be passed directly or via kwargs. **kwargs: Additional data from slot. If 'data' is not provided, will use kwargs or the first value. """ # Extract data using Routine helper method data = self._extract_input_data(data, **kwargs) # Track statistics # Operation tracking removed - use JobState for execution state original_type = type(data).__name__ rendered_text = self._render_object(data) # Emit result self.emit("output", rendered_text=rendered_text, original_type=original_type) def _render_object(self, data: Any, indent_level: int = 0, visited: set | None = None) -> str: """Recursively render an object. Args: data: Object to render. indent_level: Current indentation level. visited: Set of visited object IDs to prevent circular references. Returns: Rendered text string. """ # Initialize visited set on first call if visited is None: visited = set() # Prevent circular references obj_id = id(data) if obj_id in visited: return "[Circular Reference]" visited.add(obj_id) try: indent = self.get_config("indent", " ") * indent_level tag_format = self.get_config("tag_format", "xml") if isinstance(data, dict): lines = [] for key, value in data.items(): rendered_value = self._render_object(value, indent_level + 1, visited) if tag_format == "xml": lines.append(f"{indent}<{key}>{rendered_value}</{key}>") else: # markdown lines.append(f"{indent}**{key}**: {rendered_value}") return "\n".join(lines) if lines else "" elif isinstance(data, list): lines = [] for i, value in enumerate(data): rendered_value = self._render_object(value, indent_level + 1, visited) if tag_format == "xml": lines.append(f"{indent}<item_{i}>{rendered_value}</item_{i}>") else: # markdown lines.append(f"{indent}- {rendered_value}") return "\n".join(lines) if lines else "" else: # Primitive type return str(data) finally: # Remove from visited set when done with this branch visited.discard(obj_id)