codeplain 0.3.10.dev4__py3-none-any.whl → 0.3.11__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.
- cli_output/status.py +13 -8
- {codeplain-0.3.10.dev4.dist-info → codeplain-0.3.11.dist-info}/METADATA +4 -4
- {codeplain-0.3.10.dev4.dist-info → codeplain-0.3.11.dist-info}/RECORD +27 -21
- {codeplain-0.3.10.dev4.dist-info → codeplain-0.3.11.dist-info}/WHEEL +1 -1
- codeplain_REST_api.py +32 -1
- file_utils.py +14 -0
- git_preflight.py +41 -0
- plain2code.py +76 -5
- plain2code_console.py +5 -4
- plain_file.py +51 -18
- plain_modules.py +6 -6
- render_machine/actions/fix_conformance_test.py +11 -0
- render_machine/actions/fix_unit_tests.py +25 -0
- render_machine/render_types.py +5 -0
- tests/test_cli_output.py +15 -8
- tests/test_console_log_styles.py +61 -5
- tests/test_dry_run_link_validation.py +60 -0
- tests/test_fix_unit_tests_conformance_context.py +230 -0
- tests/test_git_preflight.py +98 -0
- tests/test_plainfileparser.py +82 -22
- tests/test_render_finished_report.py +200 -0
- tests/test_tui_components.py +147 -0
- tui/components.py +37 -10
- tui/plain2code_tui.py +9 -0
- tui/widget_helpers.py +11 -4
- {codeplain-0.3.10.dev4.dist-info → codeplain-0.3.11.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.10.dev4.dist-info → codeplain-0.3.11.dist-info}/licenses/LICENSE +0 -0
tui/components.py
CHANGED
|
@@ -3,6 +3,7 @@ from typing import Literal, Optional
|
|
|
3
3
|
|
|
4
4
|
from rich.markup import escape
|
|
5
5
|
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
6
|
+
from textual.content import Content
|
|
6
7
|
from textual.message import Message
|
|
7
8
|
from textual.timer import Timer
|
|
8
9
|
from textual.widgets import Button, Static
|
|
@@ -71,7 +72,7 @@ class CustomFooter(Horizontal):
|
|
|
71
72
|
self._footer_text_widget = Static(footer_rendering_text, classes="custom-footer-text")
|
|
72
73
|
yield self._footer_text_widget
|
|
73
74
|
if self.render_id:
|
|
74
|
-
yield Static(f"render id: {self.render_id} ", classes="custom-footer-render-id")
|
|
75
|
+
yield Static(f"render id: {self.render_id} ", classes="custom-footer-render-id", markup=False)
|
|
75
76
|
|
|
76
77
|
def update_footer_state(self, state: Literal["rendering", "pausing", "paused", "finished"]) -> None:
|
|
77
78
|
self.remove_class("footer-state-default")
|
|
@@ -201,9 +202,12 @@ class SubstateLine(Horizontal):
|
|
|
201
202
|
def _format_timer(self) -> str:
|
|
202
203
|
return format_duration_hms(self._seconds_elapsed)
|
|
203
204
|
|
|
204
|
-
def _format_line(self) ->
|
|
205
|
+
def _format_line(self) -> Content:
|
|
206
|
+
# The substate text is arbitrary (acceptance-test text from the spec, module names, ...)
|
|
207
|
+
# and may contain square brackets, so it must never go through the markup parser.
|
|
208
|
+
# Build the line as Content and style only the timer part.
|
|
205
209
|
timer = self._format_timer()
|
|
206
|
-
return f"{self.indent} └ {self.text}
|
|
210
|
+
return Content.assemble(f"{self.indent} └ {self.text} ", (f"({timer})", "#888888"))
|
|
207
211
|
|
|
208
212
|
def _refresh_timer(self) -> None:
|
|
209
213
|
try:
|
|
@@ -232,7 +236,7 @@ class ProgressItem(Vertical):
|
|
|
232
236
|
# Main row with status and description
|
|
233
237
|
with Horizontal(id=f"{self.id}-main-row", classes="progress-main-row"):
|
|
234
238
|
yield Static(self._get_status_text(self.PENDING), classes=f"status {self.PENDING}")
|
|
235
|
-
yield Static(self.initial_text, classes="description")
|
|
239
|
+
yield Static(self.initial_text, classes="description", markup=False)
|
|
236
240
|
# Substates container (full width, aligned to left)
|
|
237
241
|
yield Vertical(id=f"{self.id}-substates", classes="substates-container")
|
|
238
242
|
|
|
@@ -335,10 +339,14 @@ class ProgressItem(Vertical):
|
|
|
335
339
|
class RenderingInfoBox(Vertical):
|
|
336
340
|
"""Responsive container for module and functionality information."""
|
|
337
341
|
|
|
342
|
+
EXPAND_HINT = "(ctrl+o to expand)"
|
|
343
|
+
COLLAPSE_HINT = "(ctrl+o to collapse)"
|
|
344
|
+
|
|
338
345
|
def __init__(self, **kwargs):
|
|
339
346
|
super().__init__(**kwargs)
|
|
340
347
|
self.module_text = ""
|
|
341
348
|
self.functionality_text = ""
|
|
349
|
+
self.functionality_expanded = False
|
|
342
350
|
self.module_widget: Static | None = None
|
|
343
351
|
self.functionality_widget: Static | None = None
|
|
344
352
|
|
|
@@ -352,12 +360,31 @@ class RenderingInfoBox(Vertical):
|
|
|
352
360
|
self.functionality_text = text
|
|
353
361
|
self._refresh_content()
|
|
354
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
|
+
|
|
355
382
|
def _refresh_content(self) -> None:
|
|
356
383
|
"""Refresh text inside the box."""
|
|
357
384
|
if self.module_widget is not None:
|
|
358
385
|
self.module_widget.update(self.module_text or "")
|
|
359
386
|
if self.functionality_widget is not None:
|
|
360
|
-
self.functionality_widget.update(self.
|
|
387
|
+
self.functionality_widget.update(self._format_functionality())
|
|
361
388
|
|
|
362
389
|
def on_mount(self) -> None:
|
|
363
390
|
"""Initialize default labels on mount."""
|
|
@@ -367,8 +394,8 @@ class RenderingInfoBox(Vertical):
|
|
|
367
394
|
self._refresh_content()
|
|
368
395
|
|
|
369
396
|
def compose(self):
|
|
370
|
-
self.module_widget = Static(self.module_text, classes="rendering-info-row")
|
|
371
|
-
self.functionality_widget = Static(self.
|
|
397
|
+
self.module_widget = Static(self.module_text, classes="rendering-info-row", markup=False)
|
|
398
|
+
self.functionality_widget = Static(self._format_functionality(), classes="rendering-info-row", markup=False)
|
|
372
399
|
yield Static("module status", classes="rendering-info-title")
|
|
373
400
|
with Vertical(classes="rendering-info-box"):
|
|
374
401
|
yield self.module_widget
|
|
@@ -430,9 +457,9 @@ class TestScriptsContainer(Vertical):
|
|
|
430
457
|
def compose(self):
|
|
431
458
|
yield Static("testing status", classes="test-scripts-title")
|
|
432
459
|
with Vertical(classes="test-scripts-box"):
|
|
433
|
-
self.unit_widget = Static(self.unit_test_text, classes="test-script-row")
|
|
434
|
-
self.conformance_widget = Static(self.conformance_test_text, classes="test-script-row")
|
|
435
|
-
self.testing_widget = Static(self.testing_env_text, classes="test-script-row")
|
|
460
|
+
self.unit_widget = Static(self.unit_test_text, classes="test-script-row", markup=False)
|
|
461
|
+
self.conformance_widget = Static(self.conformance_test_text, classes="test-script-row", markup=False)
|
|
462
|
+
self.testing_widget = Static(self.testing_env_text, classes="test-script-row", markup=False)
|
|
436
463
|
yield self.unit_widget
|
|
437
464
|
yield self.conformance_widget
|
|
438
465
|
yield self.testing_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)
|
tui/widget_helpers.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
|
+
from textual.content import Content
|
|
5
6
|
from textual.css.query import NoMatches
|
|
6
7
|
from textual.widgets import Static
|
|
7
8
|
|
|
@@ -89,9 +90,14 @@ def display_success_message(tui, rendered_code_path: str):
|
|
|
89
90
|
rendered_code_path: The path to the rendered code
|
|
90
91
|
"""
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
# rendered_code_path is user-controlled and may contain square brackets, so build the
|
|
94
|
+
# message as Content instead of interpolating it into a markup string.
|
|
95
|
+
message = Content.assemble(
|
|
96
|
+
("✓ rendering completed!", "#79FC96"),
|
|
97
|
+
" ",
|
|
98
|
+
("(press enter to exit)", "#888888"),
|
|
99
|
+
"\n",
|
|
100
|
+
(f"generated code folder: {rendered_code_path} ", "#888888"),
|
|
95
101
|
)
|
|
96
102
|
|
|
97
103
|
widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static)
|
|
@@ -120,7 +126,8 @@ def transition_frid_progress(tui, from_status: str | None, to_status: str):
|
|
|
120
126
|
def display_error_message(tui, error_message: str):
|
|
121
127
|
widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static)
|
|
122
128
|
widget.add_class("error")
|
|
123
|
-
|
|
129
|
+
# Error text may contain square brackets (paths, tracebacks); keep it out of the markup parser.
|
|
130
|
+
widget.update(Content(error_message))
|
|
124
131
|
|
|
125
132
|
|
|
126
133
|
def display_usage_summary(tui, functionalities: int, render_time_seconds: float) -> None:
|
|
File without changes
|
|
File without changes
|