Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions module_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
29 changes: 23 additions & 6 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,29 @@
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

DEFAULT_TEMPLATE_DIRS = importlib.resources.files("standard_template_library")
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]
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -219,22 +232,26 @@ 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)))

if args.headless:
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:
Expand All @@ -251,6 +268,7 @@ def run_render():
css_path="styles.css",
)
app.run()

stop_event.set()
render_thread.join(timeout=RENDER_THREAD_SHUTDOWN_TIMEOUT)

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions plain2code_state.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Contains all state and context information we need for the rendering process."""

import time
import uuid
from typing import Optional

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions plain2code_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 11 additions & 14 deletions render_machine/actions/render_functional_requirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions render_machine/code_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 9 additions & 0 deletions render_machine/render_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
4 changes: 2 additions & 2 deletions render_machine/state_machine_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]:
Expand Down
Loading