From 84a81132c3f2b5b5d6dc7d9961e1f27e0b5cbc43 Mon Sep 17 00:00:00 2001 From: Nejc Stebe Date: Tue, 14 Apr 2026 10:09:35 +0200 Subject: [PATCH] add post rendering console output --- module_renderer.py | 1 + plain2code.py | 29 +++++++++++++++---- plain2code_state.py | 21 ++++++++++++++ plain2code_utils.py | 16 ++++++++++ .../actions/render_functional_requirement.py | 25 +++++++--------- render_machine/code_renderer.py | 3 ++ render_machine/render_context.py | 9 ++++++ render_machine/state_machine_config.py | 4 +-- 8 files changed, 86 insertions(+), 22 deletions(-) diff --git a/module_renderer.py b/module_renderer.py index 1e5f63b..a192892 100644 --- a/module_renderer.py +++ b/module_renderer.py @@ -275,4 +275,5 @@ def render_module(self) -> None: last_module_name = self.filename.replace(plain_file.PLAIN_SOURCE_FILE_EXTENSION, "") rendered_code_path = f"{os.path.join(self.args.build_folder, last_module_name)}/" + self.run_state.set_render_generated_code_path(rendered_code_path) self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path)) diff --git a/plain2code.py b/plain2code.py index 06dd21f..a1f0e56 100644 --- a/plain2code.py +++ b/plain2code.py @@ -47,7 +47,7 @@ get_log_file_path, ) from plain2code_state import RunState -from plain2code_utils import print_dry_run_output +from plain2code_utils import format_duration_hms, print_dry_run_output from system_config import system_config from tui.plain2code_tui import Plain2CodeTUI @@ -55,6 +55,21 @@ RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7 +def print_exit_summary( + run_state: RunState, + spec_filename: str, +) -> None: + console.quiet = False + """Print render outcome after the TUI exits (terminal restored).""" + msg = "\n[#79FC96]✓ rendering completed\n\n" if run_state.render_succeeded else "[#FF6B6B]✗ rendering failed\n\n" + msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n" + msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n" + msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n" + 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" + console.info(msg) + console.quiet = True + + def get_render_range(render_range, plain_source): render_range = render_range.split(",") 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): def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901 template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS) - console.info(f"Rendering {args.filename} to target code.") - # Compute render range from either --render-range or --render-from render_range = None if args.render_range or args.render_from: @@ -219,14 +232,16 @@ def render(args, run_state: RunState, event_bus: EventBus): # noqa: C901 ) render_error: list[Exception] = [] + run_state.render_succeeded = False def run_render(): try: module_renderer.render_module() - console.info(f"[#79FC96]Render {run_state.render_id} completed successfully.[/#79FC96]") + run_state.set_render_succeeded(True) except RenderCancelledError: pass # TUI already closed, nothing to report except Exception as e: + run_state.set_render_succeeded(False) render_error.append(e) event_bus.publish(RenderFailed(error_message=str(e))) @@ -234,7 +249,9 @@ def run_render(): console.info(f"Render started. Render ID: {run_state.render_id}") try: module_renderer.render_module() + run_state.set_render_succeeded(True) except RenderCancelledError: + run_state.set_render_succeeded(False) pass return else: @@ -251,6 +268,7 @@ def run_render(): css_path="styles.css", ) app.run() + stop_event.set() render_thread.join(timeout=RENDER_THREAD_SHUTDOWN_TIMEOUT) @@ -304,8 +322,6 @@ def main(): # noqa: C901 raise MissingAPIKey( "API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument." ) - - console.info(f"Render ID: {run_state.render_id}") render(args, run_state, event_bus) except InvalidFridArgument as e: exc_info = sys.exc_info() @@ -374,6 +390,7 @@ def main(): # noqa: C901 console.error(f"Error rendering plain code: {str(e)}\n") console.debug(f"Render ID: {run_state.render_id}") finally: + print_exit_summary(run_state, args.filename) if exc_info: # Log traceback using the logging system logging.error("Render crashed with exception:", exc_info=exc_info) diff --git a/plain2code_state.py b/plain2code_state.py index a66b7e1..8f3faab 100644 --- a/plain2code_state.py +++ b/plain2code_state.py @@ -1,5 +1,6 @@ """Contains all state and context information we need for the rendering process.""" +import time import uuid from typing import Optional @@ -9,6 +10,9 @@ class RunState: def __init__(self, spec_filename: str, replay_with: Optional[str] = None): self.replay: bool = replay_with is not None + self.render_succeeded: bool = False + self.render_generated_code_path: Optional[str] = None + self.rendered_functionalities: int = 0 if replay_with: self.render_id: str = replay_with else: @@ -17,6 +21,8 @@ def __init__(self, spec_filename: str, replay_with: Optional[str] = None): self.call_count: int = 0 self.unittest_batch_id: int = 0 self.frid_render_anaysis: dict[str, str] = {} + self.render_time_accumulated: int = 0 + self.last_render_start_timestamp: float = time.monotonic() def increment_call_count(self): self.call_count += 1 @@ -27,6 +33,21 @@ def increment_unittest_batch_id(self): def add_rendering_analysis_for_frid(self, frid, rendering_analysis) -> None: self.frid_render_anaysis[frid] = rendering_analysis + def set_render_succeeded(self, succeeded: bool): + self.render_succeeded = succeeded + + def set_render_generated_code_path(self, generated_code_path: str): + self.render_generated_code_path = generated_code_path + + def increment_rendered_functionalities(self): + self.rendered_functionalities += 1 + + def add_to_render_time(self): + self.render_time_accumulated += int(time.monotonic() - self.last_render_start_timestamp) + + def set_last_render_start_timestamp(self): + self.last_render_start_timestamp = time.monotonic() + def to_dict(self): return { "render_id": self.render_id, diff --git a/plain2code_utils.py b/plain2code_utils.py index d500d52..9a7cf41 100644 --- a/plain2code_utils.py +++ b/plain2code_utils.py @@ -3,6 +3,22 @@ import plain_spec from plain2code_console import console + +def format_duration_hms(total_seconds: int) -> str: + """Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``).""" + if total_seconds < 0: + total_seconds = 0 + h = int(total_seconds // 3600) + m = int((total_seconds % 3600) // 60) + s = total_seconds % 60 + if h: + return f"{h}h {m}m {s}s" + if m: + return f"{m}m {s}s" + text = f"{s}".rstrip("0").rstrip(".") + return f"{text}s" if text else "0s" + + AMBIGUITY_CAUSES = { "reference_resource_ambiguity": "Ambiguity is in the reference resources", "definition_ambiguity": "Ambiguity is in the definitions", diff --git a/render_machine/actions/render_functional_requirement.py b/render_machine/actions/render_functional_requirement.py index 786e50d..c2ab751 100644 --- a/render_machine/actions/render_functional_requirement.py +++ b/render_machine/actions/render_functional_requirement.py @@ -40,20 +40,17 @@ def execute(self, render_context: RenderContext, _previous_action_payload: Any | render_context, existing_files_content, "Files sent as input to code generation:" ) - with console.status( - f"[{console.INFO_STYLE}]Generating functionality {render_context.frid_context.frid}...\n" - ): - response_files = render_context.codeplain_api.render_functional_requirement( - render_context.frid_context.frid, - render_context.plain_source_tree, - render_context.frid_context.linked_resources, - existing_files_content, - memory_files_content, - render_context.module_name, - render_context.get_required_modules_functionalities(), - render_context.should_run_unit_tests(), - render_context.run_state, - ) + response_files = render_context.codeplain_api.render_functional_requirement( + render_context.frid_context.frid, + render_context.plain_source_tree, + render_context.frid_context.linked_resources, + existing_files_content, + memory_files_content, + render_context.module_name, + render_context.get_required_modules_functionalities(), + render_context.should_run_unit_tests(), + render_context.run_state, + ) except FunctionalRequirementTooComplex as e: 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)})." if e.proposed_breakdown: diff --git a/render_machine/code_renderer.py b/render_machine/code_renderer.py index 30ead21..7869012 100644 --- a/render_machine/code_renderer.py +++ b/render_machine/code_renderer.py @@ -43,8 +43,11 @@ def run(self): if self.render_context.enter_pause_event.is_set(): self.render_context.event_bus.publish(RenderPaused()) + # don't take sleep time into account for render time + self.render_context.run_state.add_to_render_time() while self.render_context.enter_pause_event.is_set(): time.sleep(PAUSE_POLL_INTERVAL_SECONDS) + self.render_context.run_state.set_last_render_start_timestamp() self.render_context.event_bus.publish( RenderStateUpdated( diff --git a/render_machine/render_context.py b/render_machine/render_context.py index c523364..e4fb084 100644 --- a/render_machine/render_context.py +++ b/render_machine/render_context.py @@ -191,6 +191,7 @@ def check_frid_iteration_limit(self): def finish_implementing_frid(self): self.functional_requirements_render_attempts_failed_unit_during_conformance_tests = 0 + self.run_state.increment_rendered_functionalities() def should_run_unit_tests(self) -> bool: return self.unittests_script is not None @@ -453,3 +454,11 @@ def finish_fixing_conformance_tests(self): console.info( f"Running conformance tests attempt {self.conformance_tests_running_context.fix_attempts + 1}." ) + + def start_render_completed(self): + self.run_state.set_render_succeeded(True) + self.run_state.add_to_render_time() + + def start_render_failed(self): + self.run_state.set_render_succeeded(False) + self.run_state.add_to_render_time() diff --git a/render_machine/state_machine_config.py b/render_machine/state_machine_config.py index 8614078..d1afe40 100644 --- a/render_machine/state_machine_config.py +++ b/render_machine/state_machine_config.py @@ -233,8 +233,8 @@ def get_states(self, render_context: RenderContext) -> List[Any]: States.FRID_FULLY_IMPLEMENTED.value, ], }, - States.RENDER_COMPLETED.value, - States.RENDER_FAILED.value, + {"name": States.RENDER_COMPLETED.value, "on_enter": "start_render_completed"}, + {"name": States.RENDER_FAILED.value, "on_enter": "start_render_failed"}, ] def get_transitions(self) -> List[Dict[str, Any]]: