variable_display.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from __future__ import annotations
  2. from typing import TYPE_CHECKING
  3. from rich.console import Console
  4. from rich.table import Table
  5. if TYPE_CHECKING:
  6. from . import DisplayManager
  7. from ..template import Template
  8. console = Console()
  9. class VariableDisplayManager:
  10. """Handles all variable-related rendering.
  11. This manager is responsible for displaying variables, sections,
  12. and their values with appropriate formatting based on context.
  13. """
  14. def __init__(self, parent: "DisplayManager"):
  15. """Initialize VariableDisplayManager.
  16. Args:
  17. parent: Reference to parent DisplayManager for accessing shared resources
  18. """
  19. self.parent = parent
  20. def render_variable_value(
  21. self,
  22. variable,
  23. context: str = "default",
  24. is_dimmed: bool = False,
  25. var_satisfied: bool = True,
  26. ) -> str:
  27. """Render variable value with appropriate formatting based on context.
  28. Args:
  29. variable: Variable instance to render
  30. context: Display context ("default", "override", "disabled")
  31. is_dimmed: Whether the variable should be dimmed
  32. var_satisfied: Whether the variable's dependencies are satisfied
  33. Returns:
  34. Formatted string representation of the variable value
  35. """
  36. from . import IconManager
  37. # Handle disabled bool variables
  38. if (is_dimmed or not var_satisfied) and variable.type == "bool":
  39. if (
  40. hasattr(variable, "_original_disabled")
  41. and variable._original_disabled is not False
  42. ):
  43. return (
  44. f"{variable._original_disabled} {IconManager.arrow_right()} False"
  45. )
  46. return "False"
  47. # Handle config overrides with arrow
  48. if (
  49. variable.origin == "config"
  50. and hasattr(variable, "_original_stored")
  51. and variable.original_value != variable.value
  52. ):
  53. settings = self.parent.settings
  54. orig = self._format_value(
  55. variable,
  56. variable.original_value,
  57. max_length=settings.VALUE_MAX_LENGTH_SHORT,
  58. )
  59. curr = variable.get_display_value(
  60. mask_sensitive=True,
  61. max_length=settings.VALUE_MAX_LENGTH_SHORT,
  62. show_none=False,
  63. )
  64. if not curr:
  65. curr = (
  66. str(variable.value)
  67. if variable.value
  68. else settings.TEXT_EMPTY_OVERRIDE
  69. )
  70. return f"{orig} [bold {settings.COLOR_WARNING}]{IconManager.arrow_right()} {curr}[/bold {settings.COLOR_WARNING}]"
  71. # Default formatting
  72. settings = self.parent.settings
  73. value = variable.get_display_value(
  74. mask_sensitive=True,
  75. max_length=settings.VALUE_MAX_LENGTH_DEFAULT,
  76. show_none=True,
  77. )
  78. if not variable.value:
  79. return f"[{settings.COLOR_MUTED}]{value}[/{settings.COLOR_MUTED}]"
  80. return value
  81. def _format_value(self, variable, value, max_length: int | None = None) -> str:
  82. """Helper to format a specific value.
  83. Args:
  84. variable: Variable instance
  85. value: Value to format
  86. max_length: Maximum length before truncation
  87. Returns:
  88. Formatted value string
  89. """
  90. settings = self.parent.settings
  91. if variable.sensitive:
  92. return settings.SENSITIVE_MASK
  93. if value is None or value == "":
  94. return f"[{settings.COLOR_MUTED}]({settings.TEXT_EMPTY_VALUE})[/{settings.COLOR_MUTED}]"
  95. val_str = str(value)
  96. return self.parent._truncate_value(val_str, max_length)
  97. def render_section(self, title: str, description: str | None) -> None:
  98. """Display a section header.
  99. Args:
  100. title: Section title
  101. description: Optional section description
  102. """
  103. settings = self.parent.settings
  104. if description:
  105. console.print(
  106. f"\n[{settings.STYLE_SECTION_TITLE}]{title}[/{settings.STYLE_SECTION_TITLE}] [{settings.STYLE_SECTION_DESC}]- {description}[/{settings.STYLE_SECTION_DESC}]"
  107. )
  108. else:
  109. console.print(
  110. f"\n[{settings.STYLE_SECTION_TITLE}]{title}[/{settings.STYLE_SECTION_TITLE}]"
  111. )
  112. console.print(
  113. settings.SECTION_SEPARATOR_CHAR * settings.SECTION_SEPARATOR_LENGTH,
  114. style=settings.COLOR_MUTED,
  115. )
  116. def _render_section_header(
  117. self, section, is_dimmed: bool, has_dependencies: bool
  118. ) -> str:
  119. """Build section header text with appropriate styling.
  120. Args:
  121. section: VariableSection instance
  122. is_dimmed: Whether section is dimmed (disabled)
  123. has_dependencies: Whether section has dependency requirements
  124. Returns:
  125. Formatted header text with Rich markup
  126. """
  127. settings = self.parent.settings
  128. disabled_text = (
  129. settings.LABEL_DISABLED if (is_dimmed and not has_dependencies) else ""
  130. )
  131. if is_dimmed:
  132. required_part = " (required)" if section.required else ""
  133. return f"[bold {settings.STYLE_DISABLED}]{section.title}{required_part}{disabled_text}[/bold {settings.STYLE_DISABLED}]"
  134. else:
  135. required_text = settings.LABEL_REQUIRED if section.required else ""
  136. return f"[bold]{section.title}{required_text}{disabled_text}[/bold]"
  137. def _render_variable_row(
  138. self, var_name: str, variable, is_dimmed: bool, var_satisfied: bool
  139. ) -> tuple:
  140. """Build variable row data for table display.
  141. Args:
  142. var_name: Variable name
  143. variable: Variable instance
  144. is_dimmed: Whether containing section is dimmed
  145. var_satisfied: Whether variable dependencies are satisfied
  146. Returns:
  147. Tuple of (var_display, type, default_val, description, row_style)
  148. """
  149. from . import IconManager
  150. settings = self.parent.settings
  151. # Build row style
  152. row_style = (
  153. settings.STYLE_DISABLED if (is_dimmed or not var_satisfied) else None
  154. )
  155. # Build default value
  156. default_val = self.render_variable_value(
  157. variable, is_dimmed=is_dimmed, var_satisfied=var_satisfied
  158. )
  159. # Build variable display name
  160. sensitive_icon = f" {IconManager.lock()}" if variable.sensitive else ""
  161. required_indicator = settings.LABEL_REQUIRED if variable.required else ""
  162. var_display = (
  163. f"{settings.VAR_NAME_INDENT}{var_name}{sensitive_icon}{required_indicator}"
  164. )
  165. return (
  166. var_display,
  167. variable.type or "str",
  168. default_val,
  169. variable.description or "",
  170. row_style,
  171. )
  172. def render_variables_table(self, template: "Template") -> None:
  173. """Display a table of variables for a template.
  174. All variables and sections are always shown. Disabled sections/variables
  175. are displayed with dimmed styling.
  176. Args:
  177. template: Template instance
  178. """
  179. if not (template.variables and template.variables.has_sections()):
  180. return
  181. settings = self.parent.settings
  182. console.print()
  183. console.print(
  184. f"[{settings.STYLE_HEADER}]Template Variables:[/{settings.STYLE_HEADER}]"
  185. )
  186. variables_table = Table(
  187. show_header=True, header_style=settings.STYLE_TABLE_HEADER
  188. )
  189. variables_table.add_column(
  190. "Variable", style=settings.STYLE_VAR_COL_NAME, no_wrap=True
  191. )
  192. variables_table.add_column("Type", style=settings.STYLE_VAR_COL_TYPE)
  193. variables_table.add_column("Default", style=settings.STYLE_VAR_COL_DEFAULT)
  194. variables_table.add_column("Description", style=settings.STYLE_VAR_COL_DESC)
  195. first_section = True
  196. for section in template.variables.get_sections().values():
  197. if not section.variables:
  198. continue
  199. if not first_section:
  200. variables_table.add_row("", "", "", "", style=settings.STYLE_DISABLED)
  201. first_section = False
  202. # Check if section is enabled AND dependencies are satisfied
  203. is_enabled = section.is_enabled()
  204. dependencies_satisfied = template.variables.is_section_satisfied(
  205. section.key
  206. )
  207. is_dimmed = not (is_enabled and dependencies_satisfied)
  208. has_dependencies = section.needs and len(section.needs) > 0
  209. # Render section header
  210. header_text = self._render_section_header(
  211. section, is_dimmed, has_dependencies
  212. )
  213. variables_table.add_row(header_text, "", "", "")
  214. # Render variables
  215. for var_name, variable in section.variables.items():
  216. # Skip toggle variable in required sections
  217. if section.required and section.toggle and var_name == section.toggle:
  218. continue
  219. # Check if variable's needs are satisfied
  220. var_satisfied = template.variables.is_variable_satisfied(var_name)
  221. # Build and add row
  222. row_data = self._render_variable_row(
  223. var_name, variable, is_dimmed, var_satisfied
  224. )
  225. variables_table.add_row(*row_data)
  226. console.print(variables_table)