codeplain 0.3.11.dev20__py3-none-any.whl → 0.3.11.dev21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeplain
3
- Version: 0.3.11.dev20
3
+ Version: 0.3.11.dev21
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -105,23 +105,23 @@ tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBu
105
105
  tests/test_script_path_resolution.py,sha256=tiEqMevQRXr9YpfHN0xldOOQpLAUgnSqTzeZWkKHaUY,4156
106
106
  tests/test_system_config.py,sha256=gH5qs9q4lh5CljAdz28HUrJzuKqCvkZherFuv7enMak,2787
107
107
  tests/test_telemetry.py,sha256=LINvx1_Oz1OJ3ho2RcIcAA29IsNfUbkVVCbFaQcrBaA,8133
108
- tests/test_tui_components.py,sha256=a8MMZmJNU4EcT1GoOAGkb2gObK0h2AQvufbPCkTMkJk,3715
108
+ tests/test_tui_components.py,sha256=O_PB6z_Xt7YWebsD21b99PVgFtqq5c03lVtYytIT5Ps,6200
109
109
  tests/test_tui_usage.py,sha256=sfiAGTpRrT3MRr0-8DH3qjuECSxjODVgwzrK_K3iPXs,7959
110
110
  tests/test_usage_summary.py,sha256=Ps0kVzvmdZYeXrBR-cqkn3szTV5neYKs1PmmURngeIc,1079
111
111
  tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
112
112
  tests/e2e/test_hello_world_python.py,sha256=2Cgs3-79dtQh5gVab257tFDHXTznefZeYqbTjlM4Nng,1632
113
113
  tests/e2e/test_hello_world_python_windows.py,sha256=ggynH_Wm4011M500zoPNNgvD-s4vZ3hvlorf1yARgQQ,2019
114
114
  tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
115
- tui/components.py,sha256=eU0t2s5SFfTAjjCYpvh9c9eakpWFJwodBKkiFwz15DU,26200
115
+ tui/components.py,sha256=tAgP7paI8bR6XAWOWyiUsKexRurEx1CNz_4z81Ty0G4,27195
116
116
  tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
117
- tui/plain2code_tui.py,sha256=8F7ZYRpmTiWRdTeQ63LPf2LGrreYjIYXvf_9XNSPyHs,17104
117
+ tui/plain2code_tui.py,sha256=b6FOEXt_dcVhpfOrAHvg-Rvn0bSH18moy5zmUaz3NS4,17552
118
118
  tui/plain_module_render_choice_tui.py,sha256=XwBMMzyDat9FYqMkKdIV7kmIevddfwGA5Qk9kgy5sYY,12112
119
119
  tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
120
120
  tui/state_handlers.py,sha256=zbUS_D9eU8qIwVpaxPEp9zL9bpg8gXsWNoUaqg9TLrQ,16664
121
121
  tui/styles.css,sha256=kiC7Og_2G1E7owoiZgV95-3_dSeQnsNj3qzaY04xkDg,6727
122
122
  tui/widget_helpers.py,sha256=jitm2WHiKj-NVFL-hULxWzckPreRbUTl9OIZLN31-ek,6997
123
- codeplain-0.3.11.dev20.dist-info/METADATA,sha256=O9c6YPWNtbYueXEKXIA4j3P_tzNcJQA8D0wlQmym5gQ,7257
124
- codeplain-0.3.11.dev20.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
125
- codeplain-0.3.11.dev20.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
126
- codeplain-0.3.11.dev20.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
127
- codeplain-0.3.11.dev20.dist-info/RECORD,,
123
+ codeplain-0.3.11.dev21.dist-info/METADATA,sha256=13HswQFYWExr96sPoJRzhzNlaHMus3rutS_9Ba8Z13Y,7257
124
+ codeplain-0.3.11.dev21.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
125
+ codeplain-0.3.11.dev21.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
126
+ codeplain-0.3.11.dev21.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
127
+ codeplain-0.3.11.dev21.dist-info/RECORD,,
@@ -6,7 +6,7 @@ from textual.widgets import Static
6
6
 
7
7
  from event_bus import EventBus
8
8
  from plain2code_state import RunState
9
- from tui.components import ProgressItem, SubstateLine, TUIComponents
9
+ from tui.components import FRIDProgress, ProgressItem, RenderingInfoBox, SubstateLine, TUIComponents
10
10
  from tui.models import Substate
11
11
  from tui.plain2code_tui import Plain2CodeTUI
12
12
  from tui.widget_helpers import display_error_message, display_success_message, update_progress_item_substates
@@ -86,3 +86,62 @@ def test_status_messages_keep_brackets():
86
86
  assert "[Errno 2] No such file: run_tests.sh [b" in str(status.content)
87
87
 
88
88
  asyncio.run(scenario())
89
+
90
+
91
+ # A functionality quote as the render TUI receives it: several lines, square brackets included.
92
+ MULTILINE_FUNCTIONALITY_TEXT = (
93
+ "Functionality 3: :User: should be able to add a :Task: [optional]\n"
94
+ " - The :Task: must have non-empty content.\n"
95
+ " - The :Task: is appended to the end of the list."
96
+ )
97
+
98
+
99
+ def test_functionality_text_collapses_and_expands_with_ctrl_o():
100
+ async def scenario():
101
+ event_bus = EventBus()
102
+ run_state = RunState(spec_filename="x.plain")
103
+ app = _make_app(run_state, event_bus)
104
+ async with app.run_test() as pilot:
105
+ frid_progress = app.query_one(f"#{TUIComponents.FRID_PROGRESS.value}", FRIDProgress)
106
+ info_box = frid_progress.query_one(RenderingInfoBox)
107
+ info_box.update_functionality(MULTILINE_FUNCTIONALITY_TEXT)
108
+ await pilot.pause()
109
+
110
+ widget = info_box.functionality_widget
111
+ assert widget is not None
112
+
113
+ # Collapsed by default: first line only, with the expand hint.
114
+ collapsed = str(widget.content)
115
+ assert "Functionality 3: :User: should be able to add a :Task: [optional]" in collapsed
116
+ assert "non-empty content" not in collapsed
117
+ assert RenderingInfoBox.EXPAND_HINT in collapsed
118
+
119
+ await pilot.press("ctrl+o")
120
+ await pilot.pause()
121
+ expanded = str(widget.content)
122
+ assert "non-empty content" in expanded
123
+ assert "appended to the end of the list" in expanded
124
+ assert RenderingInfoBox.COLLAPSE_HINT in expanded
125
+
126
+ await pilot.press("ctrl+o")
127
+ await pilot.pause()
128
+ assert str(widget.content) == collapsed
129
+
130
+ asyncio.run(scenario())
131
+
132
+
133
+ def test_single_line_functionality_text_has_no_hint():
134
+ async def scenario():
135
+ event_bus = EventBus()
136
+ run_state = RunState(spec_filename="x.plain")
137
+ app = _make_app(run_state, event_bus)
138
+ async with app.run_test() as pilot:
139
+ info_box = app.query_one(f"#{TUIComponents.FRID_PROGRESS.value}", FRIDProgress).query_one(RenderingInfoBox)
140
+ info_box.update_functionality("Functionality 1: :User: should be able to add a :Task:")
141
+ await pilot.pause()
142
+
143
+ widget = info_box.functionality_widget
144
+ assert widget is not None
145
+ assert str(widget.content) == "Functionality 1: :User: should be able to add a :Task:"
146
+
147
+ asyncio.run(scenario())
tui/components.py CHANGED
@@ -339,10 +339,14 @@ class ProgressItem(Vertical):
339
339
  class RenderingInfoBox(Vertical):
340
340
  """Responsive container for module and functionality information."""
341
341
 
342
+ EXPAND_HINT = "(ctrl+o to expand)"
343
+ COLLAPSE_HINT = "(ctrl+o to collapse)"
344
+
342
345
  def __init__(self, **kwargs):
343
346
  super().__init__(**kwargs)
344
347
  self.module_text = ""
345
348
  self.functionality_text = ""
349
+ self.functionality_expanded = False
346
350
  self.module_widget: Static | None = None
347
351
  self.functionality_widget: Static | None = None
348
352
 
@@ -356,12 +360,31 @@ class RenderingInfoBox(Vertical):
356
360
  self.functionality_text = text
357
361
  self._refresh_content()
358
362
 
363
+ def toggle_functionality(self) -> None:
364
+ """Expand or collapse the functionality text."""
365
+ self.functionality_expanded = not self.functionality_expanded
366
+ self._refresh_content()
367
+
368
+ def _format_functionality(self) -> Content:
369
+ """Build the functionality line, collapsed to its first line unless expanded.
370
+
371
+ The functionality text comes from the spec and may contain square brackets, so it
372
+ is assembled as Content and never goes through the markup parser.
373
+ """
374
+ text = self.functionality_text or ""
375
+ lines = text.splitlines()
376
+ if len(lines) <= 1:
377
+ return Content(text)
378
+ if self.functionality_expanded:
379
+ return Content.assemble(f"{text}\n", (self.COLLAPSE_HINT, "#888888"))
380
+ return Content.assemble(f"{lines[0]} \u2026 ", (self.EXPAND_HINT, "#888888"))
381
+
359
382
  def _refresh_content(self) -> None:
360
383
  """Refresh text inside the box."""
361
384
  if self.module_widget is not None:
362
385
  self.module_widget.update(self.module_text or "")
363
386
  if self.functionality_widget is not None:
364
- self.functionality_widget.update(self.functionality_text or "")
387
+ self.functionality_widget.update(self._format_functionality())
365
388
 
366
389
  def on_mount(self) -> None:
367
390
  """Initialize default labels on mount."""
@@ -372,7 +395,7 @@ class RenderingInfoBox(Vertical):
372
395
 
373
396
  def compose(self):
374
397
  self.module_widget = Static(self.module_text, classes="rendering-info-row", markup=False)
375
- self.functionality_widget = Static(self.functionality_text, classes="rendering-info-row", markup=False)
398
+ self.functionality_widget = Static(self._format_functionality(), classes="rendering-info-row", markup=False)
376
399
  yield Static("module status", classes="rendering-info-title")
377
400
  with Vertical(classes="rendering-info-box"):
378
401
  yield self.module_widget
tui/plain2code_tui.py CHANGED
@@ -65,6 +65,7 @@ class Plain2CodeTUI(App):
65
65
  Binding("ctrl+d", "quit", "Quit", show=False),
66
66
  Binding("enter", "enter_exit", "Exit", show=False),
67
67
  Binding("ctrl+p", "pause", "Pause", show=False, priority=True),
68
+ Binding("ctrl+o", "toggle_functionality", "Expand/Collapse", show=False),
68
69
  ("ctrl+l", "toggle_logs", "Toggle Logs"),
69
70
  ]
70
71
 
@@ -231,6 +232,14 @@ class Plain2CodeTUI(App):
231
232
  self.run_state.render_time_accumulated,
232
233
  )
233
234
 
235
+ def action_toggle_functionality(self) -> None:
236
+ """Expand or collapse the functionality text in the rendering info box."""
237
+ try:
238
+ frid_progress = self.query_one(f"#{TUIComponents.FRID_PROGRESS.value}", FRIDProgress)
239
+ frid_progress.query_one(RenderingInfoBox).toggle_functionality()
240
+ except NoMatches:
241
+ pass
242
+
234
243
  def action_toggle_logs(self) -> None:
235
244
  """Toggle between dashboard and log view."""
236
245
  switcher = self.query_one(f"#{TUIComponents.CONTENT_SWITCHER.value}", ContentSwitcher)