display_variable.py 9.4 KB

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