Skip to content

Commit 2d23644

Browse files
committed
add post rendering console output
1 parent 7b3f831 commit 2d23644

8 files changed

Lines changed: 86 additions & 22 deletions

module_renderer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,5 @@ def render_module(self) -> None:
275275
last_module_name = self.filename.replace(plain_file.PLAIN_SOURCE_FILE_EXTENSION, "")
276276
rendered_code_path = f"{os.path.join(self.args.build_folder, last_module_name)}/"
277277

278+
self.run_state.set_render_generated_code_path(rendered_code_path)
278279
self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path))

plain2code.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,29 @@
4747
get_log_file_path,
4848
)
4949
from plain2code_state import RunState
50-
from plain2code_utils import print_dry_run_output
50+
from plain2code_utils import format_duration_hms, print_dry_run_output
5151
from system_config import system_config
5252
from tui.plain2code_tui import Plain2CodeTUI
5353

5454
DEFAULT_TEMPLATE_DIRS = importlib.resources.files("standard_template_library")
5555
RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
5656

5757

58+
def print_exit_summary(
59+
run_state: RunState,
60+
spec_filename: str,
61+
) -> None:
62+
console.quiet = False
63+
"""Print render outcome after the TUI exits (terminal restored)."""
64+
msg = "\n[#79FC96]✓ rendering completed\n\n" if run_state.render_succeeded else "[#FF6B6B]✗ rendering failed\n\n"
65+
msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
66+
msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
67+
msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
68+
msg += f"[#8E8F91]functionalities [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]used credits [#FFFFFF]{run_state.rendered_functionalities} [#8E8F91]render time [#FFFFFF]{format_duration_hms(run_state.render_time_accumulated)}\n"
69+
console.info(msg)
70+
console.quiet = True
71+
72+
5873
def get_render_range(render_range, plain_source):
5974
render_range = render_range.split(",")
6075
range_end = render_range[1] if len(render_range) == 2 else render_range[0]
@@ -186,8 +201,6 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
186201
def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
187202
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
188203

189-
console.info(f"Rendering {args.filename} to target code.")
190-
191204
# Compute render range from either --render-range or --render-from
192205
render_range = None
193206
if args.render_range or args.render_from:
@@ -219,22 +232,26 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901
219232
)
220233

221234
render_error: list[Exception] = []
235+
run_state.render_succeeded = False
222236

223237
def run_render():
224238
try:
225239
module_renderer.render_module()
226-
console.info(f"[#79FC96]Render {run_state.render_id} completed successfully.[/#79FC96]")
240+
run_state.set_render_succeeded(True)
227241
except RenderCancelledError:
228242
pass # TUI already closed, nothing to report
229243
except Exception as e:
244+
run_state.set_render_succeeded(False)
230245
render_error.append(e)
231246
event_bus.publish(RenderFailed(error_message=str(e)))
232247

233248
if args.headless:
234249
console.info(f"Render started. Render ID: {run_state.render_id}")
235250
try:
236251
module_renderer.render_module()
252+
run_state.set_render_succeeded(True)
237253
except RenderCancelledError:
254+
run_state.set_render_succeeded(False)
238255
pass
239256
return
240257
else:
@@ -251,6 +268,7 @@ def run_render():
251268
css_path="styles.css",
252269
)
253270
app.run()
271+
254272
stop_event.set()
255273
render_thread.join(timeout=RENDER_THREAD_SHUTDOWN_TIMEOUT)
256274

@@ -304,8 +322,6 @@ def main(): # noqa: C901
304322
raise MissingAPIKey(
305323
"API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument."
306324
)
307-
308-
console.info(f"Render ID: {run_state.render_id}")
309325
render(args, run_state, event_bus)
310326
except InvalidFridArgument as e:
311327
exc_info = sys.exc_info()
@@ -374,6 +390,7 @@ def main(): # noqa: C901
374390
console.error(f"Error rendering plain code: {str(e)}\n")
375391
console.debug(f"Render ID: {run_state.render_id}")
376392
finally:
393+
print_exit_summary(run_state, args.filename)
377394
if exc_info:
378395
# Log traceback using the logging system
379396
logging.error("Render crashed with exception:", exc_info=exc_info)

plain2code_state.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Contains all state and context information we need for the rendering process."""
22

3+
import time
34
import uuid
45
from typing import Optional
56

@@ -9,6 +10,9 @@ class RunState:
910

1011
def __init__(self, spec_filename: str, replay_with: Optional[str] = None):
1112
self.replay: bool = replay_with is not None
13+
self.render_succeeded: bool = False
14+
self.render_generated_code_path: Optional[str] = None
15+
self.rendered_functionalities: int = 0
1216
if replay_with:
1317
self.render_id: str = replay_with
1418
else:
@@ -17,6 +21,8 @@ def __init__(self, spec_filename: str, replay_with: Optional[str] = None):
1721
self.call_count: int = 0
1822
self.unittest_batch_id: int = 0
1923
self.frid_render_anaysis: dict[str, str] = {}
24+
self.render_time_accumulated: int = 0
25+
self.last_render_start_timestamp: float = time.monotonic()
2026

2127
def increment_call_count(self):
2228
self.call_count += 1
@@ -27,6 +33,21 @@ def increment_unittest_batch_id(self):
2733
def add_rendering_analysis_for_frid(self, frid, rendering_analysis) -> None:
2834
self.frid_render_anaysis[frid] = rendering_analysis
2935

36+
def set_render_succeeded(self, succeeded: bool):
37+
self.render_succeeded = succeeded
38+
39+
def set_render_generated_code_path(self, generated_code_path: str):
40+
self.render_generated_code_path = generated_code_path
41+
42+
def increment_rendered_functionalities(self):
43+
self.rendered_functionalities += 1
44+
45+
def add_to_render_time(self):
46+
self.render_time_accumulated += int(time.monotonic() - self.last_render_start_timestamp)
47+
48+
def set_last_render_start_timestamp(self):
49+
self.last_render_start_timestamp = time.monotonic()
50+
3051
def to_dict(self):
3152
return {
3253
"render_id": self.render_id,

plain2code_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33
import plain_spec
44
from plain2code_console import console
55

6+
7+
def format_duration_hms(total_seconds: int) -> str:
8+
"""Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``)."""
9+
if total_seconds < 0:
10+
total_seconds = 0
11+
h = int(total_seconds // 3600)
12+
m = int((total_seconds % 3600) // 60)
13+
s = total_seconds % 60
14+
if h:
15+
return f"{h}h {m}m {s}s"
16+
if m:
17+
return f"{m}m {s}s"
18+
text = f"{s}".rstrip("0").rstrip(".")
19+
return f"{text}s" if text else "0s"
20+
21+
622
AMBIGUITY_CAUSES = {
723
"reference_resource_ambiguity": "Ambiguity is in the reference resources",
824
"definition_ambiguity": "Ambiguity is in the definitions",

render_machine/actions/render_functional_requirement.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,17 @@ def execute(self, render_context: RenderContext, _previous_action_payload: Any |
4040
render_context, existing_files_content, "Files sent as input to code generation:"
4141
)
4242

43-
with console.status(
44-
f"[{console.INFO_STYLE}]Generating functionality {render_context.frid_context.frid}...\n"
45-
):
46-
response_files = render_context.codeplain_api.render_functional_requirement(
47-
render_context.frid_context.frid,
48-
render_context.plain_source_tree,
49-
render_context.frid_context.linked_resources,
50-
existing_files_content,
51-
memory_files_content,
52-
render_context.module_name,
53-
render_context.get_required_modules_functionalities(),
54-
render_context.should_run_unit_tests(),
55-
render_context.run_state,
56-
)
43+
response_files = render_context.codeplain_api.render_functional_requirement(
44+
render_context.frid_context.frid,
45+
render_context.plain_source_tree,
46+
render_context.frid_context.linked_resources,
47+
existing_files_content,
48+
memory_files_content,
49+
render_context.module_name,
50+
render_context.get_required_modules_functionalities(),
51+
render_context.should_run_unit_tests(),
52+
render_context.run_state,
53+
)
5754
except FunctionalRequirementTooComplex as e:
5855
error_message = f"The functionality:\n{render_context.frid_context.functional_requirement_text}\n is too complex to be implemented. Please break down the functionality into smaller parts ({str(e)})."
5956
if e.proposed_breakdown:

render_machine/code_renderer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ def run(self):
4343
if self.render_context.enter_pause_event.is_set():
4444
self.render_context.event_bus.publish(RenderPaused())
4545

46+
# don't take sleep time into account for render time
47+
self.render_context.run_state.add_to_render_time()
4648
while self.render_context.enter_pause_event.is_set():
4749
time.sleep(PAUSE_POLL_INTERVAL_SECONDS)
50+
self.render_context.run_state.set_last_render_start_timestamp()
4851

4952
self.render_context.event_bus.publish(
5053
RenderStateUpdated(

render_machine/render_context.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def check_frid_iteration_limit(self):
191191

192192
def finish_implementing_frid(self):
193193
self.functional_requirements_render_attempts_failed_unit_during_conformance_tests = 0
194+
self.run_state.increment_rendered_functionalities()
194195

195196
def should_run_unit_tests(self) -> bool:
196197
return self.unittests_script is not None
@@ -453,3 +454,11 @@ def finish_fixing_conformance_tests(self):
453454
console.info(
454455
f"Running conformance tests attempt {self.conformance_tests_running_context.fix_attempts + 1}."
455456
)
457+
458+
def start_render_completed(self):
459+
self.run_state.set_render_succeeded(True)
460+
self.run_state.add_to_render_time()
461+
462+
def start_render_failed(self):
463+
self.run_state.set_render_succeeded(False)
464+
self.run_state.add_to_render_time()

render_machine/state_machine_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ def get_states(self, render_context: RenderContext) -> List[Any]:
233233
States.FRID_FULLY_IMPLEMENTED.value,
234234
],
235235
},
236-
States.RENDER_COMPLETED.value,
237-
States.RENDER_FAILED.value,
236+
{"name": States.RENDER_COMPLETED.value, "on_enter": "start_render_completed"},
237+
{"name": States.RENDER_FAILED.value, "on_enter": "start_render_failed"},
238238
]
239239

240240
def get_transitions(self) -> List[Dict[str, Any]]:

0 commit comments

Comments
 (0)