codeplain 0.3.7__py3-none-any.whl → 0.3.9__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/render_summary.py +2 -2
- cli_output/status.py +2 -2
- {codeplain-0.3.7.dist-info → codeplain-0.3.9.dist-info}/METADATA +4 -4
- {codeplain-0.3.7.dist-info → codeplain-0.3.9.dist-info}/RECORD +50 -42
- codeplain_REST_api.py +6 -2
- git_utils.py +0 -10
- memory_management.py +2 -3
- metadata_utils.py +46 -0
- module_renderer.py +3 -6
- plain2code.py +7 -1
- plain2code_arguments.py +6 -15
- plain2code_console.py +32 -21
- plain2code_events.py +1 -0
- plain2code_logger.py +3 -7
- plain2code_state.py +17 -1
- plain2code_telemetry.py +17 -11
- plain2code_utils.py +17 -13
- plain_modules.py +73 -41
- render_machine/actions/create_dist.py +2 -2
- render_machine/actions/exit_with_error.py +2 -1
- render_machine/actions/fix_conformance_test.py +4 -2
- render_machine/actions/prepare_repositories.py +3 -10
- render_machine/actions/render_functional_requirement.py +4 -3
- render_machine/conformance_tests.py +8 -6
- render_machine/render_context.py +6 -6
- render_machine/render_utils.py +15 -10
- system_config.py +80 -1
- tests/e2e/test_hello_world_python.py +1 -1
- tests/e2e/test_hello_world_python_windows.py +1 -1
- tests/test_arg_provenance.py +3 -3
- tests/test_cli_output.py +2 -2
- tests/test_console_log_styles.py +112 -0
- tests/test_folder_path_resolution.py +12 -13
- tests/test_plain2code.py +1 -2
- tests/test_plain2code_state.py +36 -0
- tests/test_plain2code_utils.py +36 -0
- tests/test_plain_modules.py +200 -15
- tests/test_prepare_repositories_layout.py +119 -0
- tests/test_system_config.py +58 -0
- tests/test_telemetry.py +31 -10
- tests/test_tui_usage.py +209 -0
- tests/test_usage_summary.py +28 -0
- tui/components.py +29 -20
- tui/plain2code_tui.py +71 -2
- tui/styles.css +4 -0
- tui/widget_helpers.py +20 -1
- usage_summary.py +30 -0
- _version.py +0 -1
- {codeplain-0.3.7.dist-info → codeplain-0.3.9.dist-info}/WHEEL +0 -0
- {codeplain-0.3.7.dist-info → codeplain-0.3.9.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.7.dist-info → codeplain-0.3.9.dist-info}/licenses/LICENSE +0 -0
cli_output/render_summary.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
from plain2code_console import console
|
|
6
6
|
from plain2code_state import RunState
|
|
7
|
-
from
|
|
7
|
+
from usage_summary import format_usage_summary
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def print_exit_summary(
|
|
@@ -24,7 +24,7 @@ def print_exit_summary(
|
|
|
24
24
|
msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
|
|
25
25
|
msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
|
|
26
26
|
msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
|
|
27
|
-
msg +=
|
|
27
|
+
msg += format_usage_summary(run_state.rendered_functionalities, run_state.render_time_accumulated) + "\n"
|
|
28
28
|
console.print(msg)
|
|
29
29
|
|
|
30
30
|
if not run_state.render_succeeded and error_message:
|
cli_output/status.py
CHANGED
|
@@ -32,7 +32,7 @@ def _display_credit_line(plan_credits: dict) -> None:
|
|
|
32
32
|
# If naive datetime, assume UTC
|
|
33
33
|
if dt.tzinfo is None:
|
|
34
34
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
35
|
-
formatted_date = dt.strftime(
|
|
35
|
+
formatted_date = f"{dt.strftime('%b')} {dt.day}, {dt.year}"
|
|
36
36
|
|
|
37
37
|
# Check if expired
|
|
38
38
|
now = datetime.now(timezone.utc)
|
|
@@ -62,7 +62,7 @@ def _display_bucket_credit_line(bucket: dict, label: str) -> None:
|
|
|
62
62
|
# If naive datetime, assume UTC
|
|
63
63
|
if dt.tzinfo is None:
|
|
64
64
|
dt = dt.replace(tzinfo=timezone.utc)
|
|
65
|
-
formatted_date = dt.strftime(
|
|
65
|
+
formatted_date = f"{dt.strftime('%b')} {dt.day}, {dt.year}"
|
|
66
66
|
|
|
67
67
|
# Check if expired
|
|
68
68
|
now = datetime.now(timezone.utc)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -8,7 +8,7 @@ Classifier: Intended Audience :: Developers
|
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Topic :: Software Development :: Code Generators
|
|
10
10
|
Requires-Python: ==3.11.*
|
|
11
|
-
Requires-Dist: gitpython==3.1.
|
|
11
|
+
Requires-Dist: gitpython==3.1.52
|
|
12
12
|
Requires-Dist: mistletoe==1.5.1
|
|
13
13
|
Requires-Dist: networkx==3.6.1
|
|
14
14
|
Requires-Dist: python-frontmatter==1.3.0
|
|
@@ -16,7 +16,7 @@ Requires-Dist: python-liquid2==0.4.0
|
|
|
16
16
|
Requires-Dist: pyyaml==6.0.3
|
|
17
17
|
Requires-Dist: requests==2.34.2
|
|
18
18
|
Requires-Dist: rich==15.0.0
|
|
19
|
-
Requires-Dist: sentry-sdk==2.
|
|
19
|
+
Requires-Dist: sentry-sdk==2.66.0
|
|
20
20
|
Requires-Dist: textual>=7.5.0
|
|
21
21
|
Requires-Dist: tiktoken==0.13.0
|
|
22
22
|
Requires-Dist: transitions==0.9.3
|
|
@@ -24,7 +24,7 @@ Provides-Extra: dev
|
|
|
24
24
|
Requires-Dist: black==26.5.1; extra == 'dev'
|
|
25
25
|
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
26
26
|
Requires-Dist: isort==8.0.1; extra == 'dev'
|
|
27
|
-
Requires-Dist: mypy==2.
|
|
27
|
+
Requires-Dist: mypy==2.3.0; extra == 'dev'
|
|
28
28
|
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
|
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
_version.py,sha256=J0I0c7-a50EOnWXMryTu_E6xhXSYFBPjVpeYP_a3vRI,22
|
|
2
1
|
change_detection.py,sha256=CazORZrezeZSw2I6VBvieN-c2SEjieZPo6pNrKwmMVA,6915
|
|
3
|
-
codeplain_REST_api.py,sha256=
|
|
2
|
+
codeplain_REST_api.py,sha256=x-sCbbBSswID4eKXDMvv84nzWynNSbBPoyAtq__6tas,21644
|
|
4
3
|
concept_utils.py,sha256=D4qVHFBRvfJZwiIq1AD_SV0Ev-RnOMg2yGWo6Cal7PM,7913
|
|
5
4
|
diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
|
|
6
5
|
event_bus.py,sha256=g5XLwRfjrvTIcB6urjREuCJzZj5TA93hy8lKXLGXuCE,650
|
|
7
6
|
file_utils.py,sha256=MNZ5li1ofIMOYB6FCrMa_UkX6nkGCgSZuaqzhqVc1Ok,11682
|
|
8
|
-
git_utils.py,sha256=
|
|
9
|
-
memory_management.py,sha256=
|
|
10
|
-
|
|
7
|
+
git_utils.py,sha256=gCUA6_9zg19f6k_0aVcRnIVlAJSBD0Vt6takx-0pKME,16547
|
|
8
|
+
memory_management.py,sha256=SPoK0BXB9_yDyrrOKjFtOOlKkJIq0I5EXV7dE0OsE8g,5972
|
|
9
|
+
metadata_utils.py,sha256=YO17o7IAma5TT1ZN0pTwrLVr_KxFQ8APv02KqKPpI5k,1641
|
|
10
|
+
module_renderer.py,sha256=AUbjGiE6USMe-uTfbOs9gEQSEMzO9qLwGTxy3_tC_M4,6615
|
|
11
11
|
partial_rendering.py,sha256=0ChOaGytogBUEGISpbWkbjT9Hqy8pnOkJdJXcitJDgk,12219
|
|
12
12
|
path_resolution.py,sha256=0tMqU0a1g4DYhTOp7QjLVSEkv_cgasb4NI1mK809dUM,2233
|
|
13
|
-
plain2code.py,sha256=
|
|
14
|
-
plain2code_arguments.py,sha256=
|
|
15
|
-
plain2code_console.py,sha256=
|
|
16
|
-
plain2code_events.py,sha256=
|
|
13
|
+
plain2code.py,sha256=Hai5_Ho1nY0Hau7Y6QEGkYeg8NKVx5jDMtQV0UVHsLU,15706
|
|
14
|
+
plain2code_arguments.py,sha256=knY7gqfpj2IKnPSl-dEdxBUpzrbohEQXamNYkFVC00Y,17426
|
|
15
|
+
plain2code_console.py,sha256=Ifs8tmfNX31UujQ5_0sk9AJcajMG_6qhF-6QZcfbcEE,5773
|
|
16
|
+
plain2code_events.py,sha256=rgDF5PDtNr_lEPvJmd1tIQXZrzwgzZW5b-_pykugqe4,1587
|
|
17
17
|
plain2code_exceptions.py,sha256=wkkEXw2k4LCd09PIH3_Hu0HyZvtXYjFaA653Sfey4FA,2287
|
|
18
|
-
plain2code_logger.py,sha256=
|
|
18
|
+
plain2code_logger.py,sha256=NRPLVIMnCHNVl6dII9gFoIzMdr8xa7icnL0T_Fx2XLw,4839
|
|
19
19
|
plain2code_nodes.py,sha256=IwjQqJxRSJKgP6eEnkYGy3VQHIyoazgkFxAEkxkjfJ8,4426
|
|
20
20
|
plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,2235
|
|
21
|
-
plain2code_state.py,sha256=
|
|
22
|
-
plain2code_telemetry.py,sha256=
|
|
23
|
-
plain2code_utils.py,sha256=
|
|
21
|
+
plain2code_state.py,sha256=uePQlC29Pbhm1yXfdCFebONYdy6jo13gTTmXSwC11cQ,3168
|
|
22
|
+
plain2code_telemetry.py,sha256=e4UCAoFklJMDWGQsL50i7nTLiOBy8gs86A0XO4WLgPo,4597
|
|
23
|
+
plain2code_utils.py,sha256=unOxuqSQRpwG8YF6uoQhnZ3ZSTJRIUCPLdJ-1lRZR5M,1534
|
|
24
24
|
plain_file.py,sha256=k2rcq53q0kOf9_8egrZILpkrVkZdfJt7HU0gHBFskU4,35116
|
|
25
|
-
plain_modules.py,sha256=
|
|
25
|
+
plain_modules.py,sha256=VgOwEWVhp7dyRs5NM-jVaC5GKn8KzT1sWuSNkM7YLIA,17375
|
|
26
26
|
plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
|
|
27
|
-
system_config.py,sha256=
|
|
27
|
+
system_config.py,sha256=nyeDS1WkyA-5m3TflB_XMDyhBvC-tVRAG3VgJaZYG_M,3694
|
|
28
|
+
usage_summary.py,sha256=MA4WcVaAwz6IWCN4k_rgxVcwO4I157wN7BZlFi3h0R4,1282
|
|
28
29
|
cli_output/__init__.py,sha256=MBKl0YbVpWE-tBa3V5_aVbB7vbaqlIK2NsHTjRUZqis,284
|
|
29
30
|
cli_output/dry_run.py,sha256=6NKhNWPtD0CBK_QHSsSqIKLTzibTwxS9d8ltP9d1bgU,1473
|
|
30
|
-
cli_output/render_summary.py,sha256
|
|
31
|
-
cli_output/status.py,sha256=
|
|
31
|
+
cli_output/render_summary.py,sha256=-yRoI7ZZTS9IHyp1vd-sFleGXTVbxlrCkvuM5TX0Mp4,1168
|
|
32
|
+
cli_output/status.py,sha256=MvdocFXL1aA73NMp92gQb0Nd-f7-iw66XVUiHK33sEM,6194
|
|
32
33
|
config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
|
|
33
34
|
config/system_config.yaml,sha256=of5R9vukSohU9AlkGJOcAVQA1UJgxYmDy6islU6iFuE,508
|
|
34
35
|
docs/generate_cli.py,sha256=0FHVhICbM8g7ahdInTNih3hNKpty1wbV5rdGECMJj48,656
|
|
35
36
|
examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py,sha256=dwTowrHiVKKbrDv21v8xJC30Q57AXZkQasdGOO5JsBE,470
|
|
36
37
|
render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
38
|
render_machine/code_renderer.py,sha256=-vp_ltU7e3ssX9hbS8IY4ETSp-39JAuoJ3ZltKpdt1M,3959
|
|
38
|
-
render_machine/conformance_tests.py,sha256=
|
|
39
|
+
render_machine/conformance_tests.py,sha256=jvdWRx-ZURSq4JMase1svKzYJ98Lc4svPvfHXglHeFE,7163
|
|
39
40
|
render_machine/implementation_code_helpers.py,sha256=LdHNLoKgVtrDaGH2APX8C9vjNjLBUhu2V7OAaJvm4rM,2009
|
|
40
|
-
render_machine/render_context.py,sha256=
|
|
41
|
+
render_machine/render_context.py,sha256=pdosCY7QMzfPy-cJxEsiM9m4I5K2CoNJ7N-5Fzg0NdU,25584
|
|
41
42
|
render_machine/render_types.py,sha256=cT3D0lmxQ3enIo0jGp3LEGCY2t3X1Q1_tqwcadzSAv0,8761
|
|
42
|
-
render_machine/render_utils.py,sha256=
|
|
43
|
+
render_machine/render_utils.py,sha256=yA3X15E_Lebk_-MxkSz9rM_qy-Pes8EQ9Ye-smkHCDw,9320
|
|
43
44
|
render_machine/state_machine_config.py,sha256=NnX5sBGkc6cNG8OOQ4PrJeJBCADRGZKJYQXV7ndR4bs,26789
|
|
44
45
|
render_machine/states.py,sha256=niFXqayyA_rezCrlzo0hYeHAruErcPzQVF-BeIBGnL8,1968
|
|
45
46
|
render_machine/triggers.py,sha256=VekNhHGR-u5JXIboqc_Eyqf73LNH7wezLK7r3WWtodk,1269
|
|
@@ -47,16 +48,16 @@ render_machine/actions/analyze_specification_ambiguity.py,sha256=SykZAozGvr1jySx
|
|
|
47
48
|
render_machine/actions/base_action.py,sha256=vVzMp-LanrZyq-jamHyWaQxpRl-pLqtTdtiEzUBhfyE,534
|
|
48
49
|
render_machine/actions/commit_conformance_tests_changes.py,sha256=08yxCPCpf5TlrzLPsEh5SJXVVvnLfwdNKSr7BlzlYxo,2589
|
|
49
50
|
render_machine/actions/commit_implementation_code_changes.py,sha256=yi51oAArLCgWcmFbynes-BK2IGnZwA7wygrTJGoL8dY,826
|
|
50
|
-
render_machine/actions/create_dist.py,sha256=
|
|
51
|
-
render_machine/actions/exit_with_error.py,sha256
|
|
51
|
+
render_machine/actions/create_dist.py,sha256=ykER7i2OvWAXjrlAZnkQHpiNl00vSMF8WDxNhlWKfsA,1088
|
|
52
|
+
render_machine/actions/exit_with_error.py,sha256=W6TMULHJcHEaXE4WUnlELEu0_l7IZy9HWaPkXyqg7ck,1279
|
|
52
53
|
render_machine/actions/finish_functional_requirement.py,sha256=usQgPSg6PHpBOR1RhK7lPz9zjY-2VJJDqi3T0nlqdLw,841
|
|
53
|
-
render_machine/actions/fix_conformance_test.py,sha256=
|
|
54
|
+
render_machine/actions/fix_conformance_test.py,sha256=EZLVjF4A8zSTmyqdgcLoz-2pLpeieq4M_Rb71pY3IKA,10095
|
|
54
55
|
render_machine/actions/fix_unit_tests.py,sha256=hvwvCJDk_-8fHMpkNFyeBSG1IshIcufDpymF00ulPXY,2301
|
|
55
|
-
render_machine/actions/prepare_repositories.py,sha256=
|
|
56
|
+
render_machine/actions/prepare_repositories.py,sha256=dnh4cYeTZS0snWEeD8gAxtW2ot3KBq4nStRG052qc9U,3124
|
|
56
57
|
render_machine/actions/prepare_testing_environment.py,sha256=kIsXgV6-jOlqp-04cimPgTQoHPrVIu0zW-H_Jj-jowE,2106
|
|
57
58
|
render_machine/actions/refactor_code.py,sha256=gyPsnqgHseL2bkENu4WeGCRyV0R18_GH3JOrPdoxKvg,2609
|
|
58
59
|
render_machine/actions/render_conformance_tests.py,sha256=5gOQn-G1T9UWEI8sDfALcVdArk0ln9DgUprOPjccS-U,9268
|
|
59
|
-
render_machine/actions/render_functional_requirement.py,sha256=
|
|
60
|
+
render_machine/actions/render_functional_requirement.py,sha256=53KzMVlcefoag1x73roCEQvQ7FbmH8FzhXMQau-s8tg,4655
|
|
60
61
|
render_machine/actions/run_conformance_tests.py,sha256=4sJTORl6YLFXQIdLMBQHDeJd2hztGyt3tsVvrCviBko,4008
|
|
61
62
|
render_machine/actions/run_unit_tests.py,sha256=A4WXtSzpCLDHYR8fQAbfxUf_E19GYflZSHZaSreoFPk,2101
|
|
62
63
|
render_machine/actions/summarize_conformance_tests.py,sha256=Vy1kRK8vyazK_BbsKuz1MLDN_TQWVxxzkwEEm-50byc,1639
|
|
@@ -66,40 +67,47 @@ standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOz
|
|
|
66
67
|
standard_template_library/typescript-react-app-template.plain,sha256=DN5wEJcT8ZqQTf0vFKi9Z1isi9cin2UVNOZb3r28d3M,715
|
|
67
68
|
tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
68
69
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
69
|
-
tests/test_arg_provenance.py,sha256=
|
|
70
|
+
tests/test_arg_provenance.py,sha256=d_J7aCeHMXBpctBWTt60WU-I8pEKN7XRXRTJZZGCdo0,3441
|
|
70
71
|
tests/test_change_detection.py,sha256=jH8SEhndX400DNqIKmcYLzxHqq9WFdgmuvY3XPq-ZFc,13534
|
|
71
|
-
tests/test_cli_output.py,sha256=
|
|
72
|
+
tests/test_cli_output.py,sha256=iKS1Li4yhoIL0ezJ58o4oEf9V8TiKcQkvQLVtJpdM4c,18420
|
|
73
|
+
tests/test_console_log_styles.py,sha256=63DQWiwWNhe_rMP70bW9DAB6-EPaqU3Wpwyh8eR09pc,4080
|
|
72
74
|
tests/test_file_utils.py,sha256=R_ZIhrTtwlhHxgmbBtA8PFEU5-QB4Fi5skJ7PS2KzdU,3614
|
|
73
|
-
tests/test_folder_path_resolution.py,sha256=
|
|
75
|
+
tests/test_folder_path_resolution.py,sha256=K9v684xkPE0HOAsovg8uDixpZmaIk7ho-Wd3rTjKLF0,6560
|
|
74
76
|
tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
|
|
75
77
|
tests/test_imports.py,sha256=1ncbqQhtQW237kIjyV-Ufl7-R-PuduvoYFj2iS-DXW4,2608
|
|
76
78
|
tests/test_log_file_name_resolution.py,sha256=99g9NgPF-BZ98CvNyqDcltBAx4sLBE8gpQPAAieKiPc,3619
|
|
77
79
|
tests/test_logging_config_path_resolution.py,sha256=avz_ehRd18INwIXvAy1AwVzNNUz0vS8eu1nRe0bra8g,2650
|
|
78
80
|
tests/test_partial_rendering.py,sha256=r0RUUd1pI4-2ZkRrM1j8_kUqv-70hrOGjuKBhZoRmH0,22758
|
|
79
81
|
tests/test_path_resolution.py,sha256=yQpd-Unq1lwNv0wkRPwVs5pDT8JmyyDgbpiTQsyt--Y,2615
|
|
80
|
-
tests/test_plain2code.py,sha256=
|
|
81
|
-
tests/
|
|
82
|
+
tests/test_plain2code.py,sha256=lyb7iYK8XHu1dmTFh9Q31YgX0faNlCHiyI5M35VQwOg,3780
|
|
83
|
+
tests/test_plain2code_state.py,sha256=cowPZS9oRbv9J49CE4P3Rc47Q5oQOby4bTf2OlnRlBI,1217
|
|
84
|
+
tests/test_plain2code_utils.py,sha256=-W8a7FquMJQ_pTdBoBF1XprzNL2G555QhdtXCy6BDyQ,1237
|
|
85
|
+
tests/test_plain_modules.py,sha256=jDjPv1JyKCgIMaoy3GYpjxTSQkfkI2ppmJgqzHdQ6E4,21592
|
|
82
86
|
tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
|
|
83
87
|
tests/test_plainfileparser.py,sha256=xd9P0KqZJlJPn3RKT510IQbfBotk6nQ9O8p9iLwJiQk,22815
|
|
84
88
|
tests/test_plainspec.py,sha256=ng55E8ajrH2-gPC1p_QDIe_zXplPvoqHJ-d57UWKFTw,3577
|
|
89
|
+
tests/test_prepare_repositories_layout.py,sha256=9T9lf92HhZC_ziUZxiyogbbeqJfmg131FZS81BuUNfs,4751
|
|
85
90
|
tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
|
|
86
91
|
tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
|
|
87
92
|
tests/test_script_path_resolution.py,sha256=tiEqMevQRXr9YpfHN0xldOOQpLAUgnSqTzeZWkKHaUY,4156
|
|
88
|
-
tests/
|
|
93
|
+
tests/test_system_config.py,sha256=poH6eviXqAxAzTcvdP0TeS0wBp6CW1IW5irqto7FNyI,1784
|
|
94
|
+
tests/test_telemetry.py,sha256=LINvx1_Oz1OJ3ho2RcIcAA29IsNfUbkVVCbFaQcrBaA,8133
|
|
95
|
+
tests/test_tui_usage.py,sha256=sfiAGTpRrT3MRr0-8DH3qjuECSxjODVgwzrK_K3iPXs,7959
|
|
96
|
+
tests/test_usage_summary.py,sha256=Ps0kVzvmdZYeXrBR-cqkn3szTV5neYKs1PmmURngeIc,1079
|
|
89
97
|
tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
|
|
90
|
-
tests/e2e/test_hello_world_python.py,sha256=
|
|
91
|
-
tests/e2e/test_hello_world_python_windows.py,sha256=
|
|
98
|
+
tests/e2e/test_hello_world_python.py,sha256=2Cgs3-79dtQh5gVab257tFDHXTznefZeYqbTjlM4Nng,1632
|
|
99
|
+
tests/e2e/test_hello_world_python_windows.py,sha256=ggynH_Wm4011M500zoPNNgvD-s4vZ3hvlorf1yARgQQ,2019
|
|
92
100
|
tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
|
|
93
|
-
tui/components.py,sha256=
|
|
101
|
+
tui/components.py,sha256=XkC4Aq6VyTnFnBQs8PEHwVdAOczhm0ebKN92kiYMbq0,25791
|
|
94
102
|
tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
|
|
95
|
-
tui/plain2code_tui.py,sha256=
|
|
103
|
+
tui/plain2code_tui.py,sha256=8F7ZYRpmTiWRdTeQ63LPf2LGrreYjIYXvf_9XNSPyHs,17104
|
|
96
104
|
tui/plain_module_render_choice_tui.py,sha256=DRl_xhUbUJsyOaCYt_uNDSLUAA3Ty9NYI9zFrVDwp9Y,11270
|
|
97
105
|
tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
98
106
|
tui/state_handlers.py,sha256=4r_6m5YMcprQtgiTP0De3XFfGAF8VzgaWXHxPweYPbQ,16325
|
|
99
|
-
tui/styles.css,sha256=
|
|
100
|
-
tui/widget_helpers.py,sha256=
|
|
101
|
-
codeplain-0.3.
|
|
102
|
-
codeplain-0.3.
|
|
103
|
-
codeplain-0.3.
|
|
104
|
-
codeplain-0.3.
|
|
105
|
-
codeplain-0.3.
|
|
107
|
+
tui/styles.css,sha256=kiC7Og_2G1E7owoiZgV95-3_dSeQnsNj3qzaY04xkDg,6727
|
|
108
|
+
tui/widget_helpers.py,sha256=ToO9MV2bF2Rp-XMNcjIYclnB_qmgO5sLw4UtQ2CyTMg,6652
|
|
109
|
+
codeplain-0.3.9.dist-info/METADATA,sha256=v3UbwneHulzIUZMJI9ZHf032CyyhAef-BBmn-RRsq14,6351
|
|
110
|
+
codeplain-0.3.9.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
111
|
+
codeplain-0.3.9.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
112
|
+
codeplain-0.3.9.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
|
|
113
|
+
codeplain-0.3.9.dist-info/RECORD,,
|
codeplain_REST_api.py
CHANGED
|
@@ -5,6 +5,7 @@ import requests
|
|
|
5
5
|
from requests.exceptions import ConnectionError, RequestException, Timeout
|
|
6
6
|
|
|
7
7
|
import plain2code_exceptions
|
|
8
|
+
from plain2code_console import RETRY_COLOR
|
|
8
9
|
from plain2code_state import RunState
|
|
9
10
|
|
|
10
11
|
MAX_RETRIES = 4
|
|
@@ -67,8 +68,11 @@ class CodeplainAPI:
|
|
|
67
68
|
connection_error_type = "Network error" if is_connection_error else "Error"
|
|
68
69
|
if attempt < num_retries:
|
|
69
70
|
if not silent:
|
|
70
|
-
self.console.debug(
|
|
71
|
-
|
|
71
|
+
self.console.debug(
|
|
72
|
+
f"↻ {connection_error_type} on attempt {attempt + 1}/{num_retries + 1}: {error}. "
|
|
73
|
+
f"Retrying in {retry_delay} seconds...",
|
|
74
|
+
color=RETRY_COLOR,
|
|
75
|
+
)
|
|
72
76
|
time.sleep(retry_delay)
|
|
73
77
|
# Exponential backoff
|
|
74
78
|
return retry_delay * 2
|
git_utils.py
CHANGED
|
@@ -64,7 +64,6 @@ def init_git_repo(
|
|
|
64
64
|
path_to_repo: Union[str, os.PathLike],
|
|
65
65
|
module_name: Optional[str] = None,
|
|
66
66
|
render_id: Optional[str] = None,
|
|
67
|
-
initial_files: Optional[dict[str, str]] = None,
|
|
68
67
|
) -> Repo:
|
|
69
68
|
"""
|
|
70
69
|
Initializes a new git repository in the given path.
|
|
@@ -79,10 +78,6 @@ def init_git_repo(
|
|
|
79
78
|
repo = Repo.init(path_to_repo)
|
|
80
79
|
_ensure_git_config(repo)
|
|
81
80
|
|
|
82
|
-
if initial_files:
|
|
83
|
-
file_utils.store_response_files(path_to_repo, initial_files, [])
|
|
84
|
-
repo.git.add(".")
|
|
85
|
-
|
|
86
81
|
repo.git.commit(
|
|
87
82
|
"--allow-empty", "-m", _get_full_commit_message(INITIAL_COMMIT_MESSAGE, module_name, None, render_id)
|
|
88
83
|
)
|
|
@@ -95,14 +90,9 @@ def clone_repo(
|
|
|
95
90
|
new_repo_path: str,
|
|
96
91
|
module_name: Optional[str] = None,
|
|
97
92
|
render_id: Optional[str] = None,
|
|
98
|
-
initial_files: Optional[dict[str, str]] = None,
|
|
99
93
|
) -> Repo:
|
|
100
94
|
repo = Repo.clone_from(source_repo_path, new_repo_path)
|
|
101
95
|
|
|
102
|
-
if initial_files:
|
|
103
|
-
file_utils.store_response_files(new_repo_path, initial_files, [])
|
|
104
|
-
repo.git.add(".")
|
|
105
|
-
|
|
106
96
|
repo.git.commit(
|
|
107
97
|
"--allow-empty", "-m", _get_full_commit_message(INITIAL_COMMIT_MESSAGE, module_name, None, render_id)
|
|
108
98
|
)
|
memory_management.py
CHANGED
|
@@ -3,7 +3,6 @@ import os
|
|
|
3
3
|
|
|
4
4
|
import file_utils
|
|
5
5
|
from plain2code_console import console
|
|
6
|
-
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER
|
|
7
6
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
8
7
|
from render_machine.render_context import RenderContext
|
|
9
8
|
|
|
@@ -24,9 +23,9 @@ class MemoryManager:
|
|
|
24
23
|
console.debug(f"Loaded {len(memory_files_content)} memory files.")
|
|
25
24
|
return memory_files, memory_files_content
|
|
26
25
|
|
|
27
|
-
def __init__(self, codeplain_api,
|
|
26
|
+
def __init__(self, codeplain_api, memory_folder: str):
|
|
28
27
|
self.codeplain_api = codeplain_api
|
|
29
|
-
self.memory_folder =
|
|
28
|
+
self.memory_folder = memory_folder
|
|
30
29
|
|
|
31
30
|
def create_conformance_tests_memory(
|
|
32
31
|
self, render_context: RenderContext, exit_code: int, conformance_tests_issue: str
|
metadata_utils.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Read/write helpers for the per-module metadata file (``module_metadata.json``).
|
|
2
|
+
|
|
3
|
+
These helpers operate purely on a metadata path or a plain dict. Module-specific
|
|
4
|
+
logic — computing the hashes, assembling the payload from the spec and required
|
|
5
|
+
modules — stays on :class:`plain_modules.PlainModule`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
MODULE_METADATA_FILENAME = "module_metadata.json"
|
|
14
|
+
MODULE_FUNCTIONALITIES = "functionalities"
|
|
15
|
+
REQUIRED_MODULES_FUNCTIONALITIES = "required_modules_functionalities"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_metadata(metadata_path: str) -> dict | None:
|
|
19
|
+
"""Return the parsed metadata dict, or None if the file does not exist."""
|
|
20
|
+
if not os.path.exists(metadata_path):
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
with open(metadata_path, "r", encoding="utf-8") as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def write_metadata(metadata_path: str, metadata: dict) -> None:
|
|
28
|
+
"""Write metadata as indented JSON, creating the parent folder if needed."""
|
|
29
|
+
os.makedirs(os.path.dirname(metadata_path), exist_ok=True)
|
|
30
|
+
|
|
31
|
+
with open(metadata_path, "w", encoding="utf-8") as f:
|
|
32
|
+
json.dump(metadata, f, indent=4)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def truncate_functionalities(metadata: dict, keep_count: int) -> bool:
|
|
36
|
+
"""Trim the functionalities list in ``metadata`` in place to ``keep_count`` entries.
|
|
37
|
+
|
|
38
|
+
Returns True if the list was shortened, False if it was already short enough
|
|
39
|
+
(in which case ``metadata`` is left untouched).
|
|
40
|
+
"""
|
|
41
|
+
functionalities = metadata.get(MODULE_FUNCTIONALITIES, [])
|
|
42
|
+
if len(functionalities) <= keep_count:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
metadata[MODULE_FUNCTIONALITIES] = functionalities[:keep_count]
|
|
46
|
+
return True
|
module_renderer.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
import os
|
|
3
2
|
import threading
|
|
4
3
|
|
|
5
4
|
from event_bus import EventBus
|
|
@@ -48,9 +47,8 @@ class ModuleRenderer:
|
|
|
48
47
|
self.codeplainAPI,
|
|
49
48
|
memory_manager,
|
|
50
49
|
plain_module,
|
|
51
|
-
build_folder=
|
|
50
|
+
build_folder=plain_module.module_build_folder,
|
|
52
51
|
build_dest=self.args.build_dest,
|
|
53
|
-
conformance_tests_folder=self.args.conformance_tests_folder,
|
|
54
52
|
conformance_tests_dest=self.args.conformance_tests_dest,
|
|
55
53
|
unittests_script=self.args.unittests_script,
|
|
56
54
|
conformance_tests_script=self.args.conformance_tests_script,
|
|
@@ -115,8 +113,7 @@ class ModuleRenderer:
|
|
|
115
113
|
|
|
116
114
|
memory_manager = MemoryManager(
|
|
117
115
|
self.codeplainAPI,
|
|
118
|
-
plain_module.
|
|
119
|
-
self.args.conformance_tests_folder,
|
|
116
|
+
plain_module.module_memory_folder,
|
|
120
117
|
)
|
|
121
118
|
render_context = self._build_render_context_for_module(
|
|
122
119
|
plain_module,
|
|
@@ -165,7 +162,7 @@ class ModuleRenderer:
|
|
|
165
162
|
if self.args.copy_build:
|
|
166
163
|
rendered_code_path = f"{self.args.build_dest}/"
|
|
167
164
|
else:
|
|
168
|
-
rendered_code_path = self.
|
|
165
|
+
rendered_code_path = self.plain_module.module_build_folder
|
|
169
166
|
|
|
170
167
|
self.run_state.set_render_generated_code_path(rendered_code_path)
|
|
171
168
|
self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path))
|
plain2code.py
CHANGED
|
@@ -212,6 +212,12 @@ def render( # noqa: C901
|
|
|
212
212
|
|
|
213
213
|
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
|
|
214
214
|
|
|
215
|
+
# The module_metadata file lives outside the code git repo. That means that a crash mid-render can leave it
|
|
216
|
+
# claiming a functionality was implemented even thought it wasn't yet committed (because of the crash).
|
|
217
|
+
# Out of precaution, this reconciles every module_metadata against the code repo.
|
|
218
|
+
for module in plain_module.all_required_modules + [plain_module]:
|
|
219
|
+
module.reconcile_metadata_with_git()
|
|
220
|
+
|
|
215
221
|
render_choice = None
|
|
216
222
|
if render_range is None:
|
|
217
223
|
plain_module_render_state = get_plain_module_render_state(plain_module)
|
|
@@ -286,6 +292,7 @@ def render( # noqa: C901
|
|
|
286
292
|
render_thread = threading.Thread(target=run_render, daemon=True)
|
|
287
293
|
app = Plain2CodeTUI(
|
|
288
294
|
event_bus=event_bus,
|
|
295
|
+
run_state=run_state,
|
|
289
296
|
on_ready=render_thread.start,
|
|
290
297
|
render_id=run_state.render_id,
|
|
291
298
|
unittests_script=args.unittests_script,
|
|
@@ -355,7 +362,6 @@ def main(): # noqa: C901
|
|
|
355
362
|
plain_module = plain_modules.PlainModule(
|
|
356
363
|
os.path.basename(args.filename),
|
|
357
364
|
args.build_folder,
|
|
358
|
-
args.conformance_tests_folder,
|
|
359
365
|
template_dirs,
|
|
360
366
|
)
|
|
361
367
|
except Exception as e:
|
plain2code_arguments.py
CHANGED
|
@@ -16,7 +16,6 @@ CODEPLAIN_API_KEY = os.getenv("CODEPLAIN_API_KEY")
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
DEFAULT_BUILD_FOLDER = "plain_modules"
|
|
19
|
-
DEFAULT_CONFORMANCE_TESTS_FOLDER = "conformance_tests"
|
|
20
19
|
DEFAULT_BUILD_DEST = "dist"
|
|
21
20
|
DEFAULT_CONFORMANCE_TESTS_DEST = "dist_conformance_tests"
|
|
22
21
|
|
|
@@ -299,23 +298,15 @@ def create_parser():
|
|
|
299
298
|
parser,
|
|
300
299
|
"--unittests-script",
|
|
301
300
|
type=str,
|
|
302
|
-
help="Shell script to run unit tests on generated code. Receives the
|
|
303
|
-
)
|
|
304
|
-
_add_arg(
|
|
305
|
-
parser,
|
|
306
|
-
"--conformance-tests-folder",
|
|
307
|
-
type=non_empty_string,
|
|
308
|
-
default=DEFAULT_CONFORMANCE_TESTS_FOLDER,
|
|
309
|
-
help="Folder for conformance test files",
|
|
310
|
-
path=True,
|
|
301
|
+
help="Shell script to run unit tests on generated code. Receives the module's code folder path as its first argument (e.g. `plain_modules/module_name/code`).",
|
|
311
302
|
)
|
|
312
303
|
_add_arg(
|
|
313
304
|
parser,
|
|
314
305
|
"--conformance-tests-script",
|
|
315
306
|
type=str,
|
|
316
307
|
help="Path to conformance tests shell script. Every conformance test script should accept two arguments: "
|
|
317
|
-
"1) Path to a folder (e.g. `plain_modules/module_name`) containing generated source code, "
|
|
318
|
-
"2) Path to a subfolder of the
|
|
308
|
+
"1) Path to a folder (e.g. `plain_modules/module_name/code`) containing generated source code, "
|
|
309
|
+
"2) Path to a subfolder of the module's tests folder (e.g. `plain_modules/module_name/tests/subfoldername`) containing test files.",
|
|
319
310
|
)
|
|
320
311
|
|
|
321
312
|
_add_arg(
|
|
@@ -400,7 +391,7 @@ def create_parser():
|
|
|
400
391
|
"--copy-conformance-tests",
|
|
401
392
|
action="store_true",
|
|
402
393
|
default=False,
|
|
403
|
-
help="If set, copy the conformance tests
|
|
394
|
+
help="If set, copy the module's conformance tests (from `<build-folder>/<module>/tests`) to `--conformance-tests-dest` folder after successful rendering. Requires --conformance-tests-script.",
|
|
404
395
|
)
|
|
405
396
|
_add_arg(
|
|
406
397
|
parser,
|
|
@@ -490,8 +481,8 @@ def parse_arguments(command_line: Optional[Sequence[str]] = None):
|
|
|
490
481
|
|
|
491
482
|
if args.build_folder == args.build_dest:
|
|
492
483
|
parser.error("--build-folder and --build-dest cannot be the same")
|
|
493
|
-
if args.
|
|
494
|
-
parser.error("--conformance-tests-
|
|
484
|
+
if args.conformance_tests_dest == args.build_folder:
|
|
485
|
+
parser.error("--conformance-tests-dest and --build-folder cannot be the same")
|
|
495
486
|
|
|
496
487
|
args.render_conformance_tests = args.conformance_tests_script is not None
|
|
497
488
|
|
plain2code_console.py
CHANGED
|
@@ -11,6 +11,12 @@ CHARACTERS_TO_TOKENS_RULE_OF_THUMB_RATIO = 4
|
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(plain2code_logger.LOGGER_NAME)
|
|
13
13
|
|
|
14
|
+
# Colors for log messages, applied by the terminal and the TUI via the "color"
|
|
15
|
+
# parameter of the console methods. The file log always receives plain text.
|
|
16
|
+
RETRY_COLOR = "#FFB454" # Amber
|
|
17
|
+
SUCCESS_COLOR = "#79FC96" # Green
|
|
18
|
+
MUTED_COLOR = "#888888" # Grey
|
|
19
|
+
|
|
14
20
|
|
|
15
21
|
class Plain2CodeConsole(Console):
|
|
16
22
|
INFO_STYLE = Style()
|
|
@@ -33,30 +39,37 @@ class Plain2CodeConsole(Console):
|
|
|
33
39
|
logger.debug(f"Exception: {e}")
|
|
34
40
|
self.llm_encoding = None
|
|
35
41
|
|
|
36
|
-
def info(self, *args, **kwargs):
|
|
37
|
-
|
|
38
|
-
super().print(*args, **kwargs, style=self.INFO_STYLE)
|
|
42
|
+
def info(self, *args, color=None, **kwargs):
|
|
43
|
+
self._log_and_print(logging.INFO, self.INFO_STYLE, args, color, kwargs)
|
|
39
44
|
|
|
40
|
-
def warning(self, *args, **kwargs):
|
|
41
|
-
|
|
42
|
-
super().print(*args, **kwargs, style=self.WARNING_STYLE)
|
|
45
|
+
def warning(self, *args, color=None, **kwargs):
|
|
46
|
+
self._log_and_print(logging.WARNING, self.WARNING_STYLE, args, color, kwargs)
|
|
43
47
|
|
|
44
|
-
def error(self, *args, **kwargs):
|
|
45
|
-
|
|
46
|
-
super().print(*args, **kwargs, style=self.ERROR_STYLE)
|
|
48
|
+
def error(self, *args, color=None, **kwargs):
|
|
49
|
+
self._log_and_print(logging.ERROR, self.ERROR_STYLE, args, color, kwargs)
|
|
47
50
|
|
|
48
|
-
def input(self, *args, **kwargs):
|
|
51
|
+
def input(self, *args, color=None, **kwargs):
|
|
49
52
|
# We also log input as info so it shows in the toggled view
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
self._log_and_print(logging.INFO, self.INPUT_STYLE, args, color, kwargs)
|
|
54
|
+
|
|
55
|
+
def output(self, *args, color=None, **kwargs):
|
|
56
|
+
self._log_and_print(logging.INFO, self.OUTPUT_STYLE, args, color, kwargs)
|
|
52
57
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
super().print(*args, **kwargs, style=self.OUTPUT_STYLE)
|
|
58
|
+
def debug(self, *args, color=None, **kwargs):
|
|
59
|
+
self._log_and_print(logging.DEBUG, self.DEBUG_STYLE, args, color, kwargs)
|
|
56
60
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
def _log_and_print(self, level, base_style, args, color, kwargs):
|
|
62
|
+
"""Log the plain message text, then print it styled to the terminal.
|
|
63
|
+
|
|
64
|
+
The optional color is applied by the terminal (via style) and forwarded to
|
|
65
|
+
the TUI as the "log_color" record attribute; the file log stays plain text.
|
|
66
|
+
"""
|
|
67
|
+
logger.log(level, " ".join(map(str, args)), extra={"log_color": color})
|
|
68
|
+
style = base_style + Style(color=color) if color else base_style
|
|
69
|
+
# Log messages must render exactly as logged: don't interpret square brackets
|
|
70
|
+
# in interpolated content (error texts, file names) as Rich markup.
|
|
71
|
+
kwargs.setdefault("markup", False)
|
|
72
|
+
super().print(*args, **kwargs, style=style)
|
|
60
73
|
|
|
61
74
|
def print_list(self, items, style=None):
|
|
62
75
|
for item in items:
|
|
@@ -130,9 +143,7 @@ class Plain2CodeConsole(Console):
|
|
|
130
143
|
for resource_name in resources_list:
|
|
131
144
|
if resource_name["target"] in linked_resources:
|
|
132
145
|
file_tokens = self._count_tokens(linked_resources[resource_name["target"]])
|
|
133
|
-
self.debug(
|
|
134
|
-
f"- {resource_name['text']} [#4169E1]({resource_name['target']}, {file_tokens} tokens)[/#4169E1]"
|
|
135
|
-
)
|
|
146
|
+
self.debug(f"- {resource_name['text']} ({resource_name['target']}, {file_tokens} tokens)")
|
|
136
147
|
|
|
137
148
|
self.input()
|
|
138
149
|
|
plain2code_events.py
CHANGED
|
@@ -44,6 +44,7 @@ class LogMessageEmitted(BaseEvent):
|
|
|
44
44
|
level: str # e.g., "INFO", "DEBUG", "ERROR"
|
|
45
45
|
message: str # The actual log message
|
|
46
46
|
timestamp: str # Formatted timestamp
|
|
47
|
+
log_color: Optional[str] = None # Color for the message (e.g. "#FFB454"); the file log stays plain text
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
@dataclass
|
plain2code_logger.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import time
|
|
3
2
|
|
|
4
3
|
from event_bus import EventBus
|
|
5
4
|
from plain2code_events import LogMessageEmitted
|
|
@@ -42,9 +41,7 @@ class ElapsedTimeFormatter(logging.Formatter):
|
|
|
42
41
|
def format(self, record):
|
|
43
42
|
# Calculate elapsed time the same way as LoggingHandler does for the TUI
|
|
44
43
|
try:
|
|
45
|
-
offset_seconds = self.run_state.
|
|
46
|
-
time.monotonic() - self.run_state.last_render_start_timestamp
|
|
47
|
-
)
|
|
44
|
+
offset_seconds = self.run_state.get_live_render_time()
|
|
48
45
|
except Exception:
|
|
49
46
|
# If RunState is not available or there's any error, default to 00:00:00
|
|
50
47
|
offset_seconds = 0
|
|
@@ -74,9 +71,7 @@ class LoggingHandler(logging.Handler):
|
|
|
74
71
|
|
|
75
72
|
def emit(self, record):
|
|
76
73
|
try:
|
|
77
|
-
offset_seconds = self.run_state.
|
|
78
|
-
time.monotonic() - self.run_state.last_render_start_timestamp
|
|
79
|
-
)
|
|
74
|
+
offset_seconds = self.run_state.get_live_render_time()
|
|
80
75
|
|
|
81
76
|
hours = offset_seconds // 3600
|
|
82
77
|
minutes = (offset_seconds % 3600) // 60
|
|
@@ -88,6 +83,7 @@ class LoggingHandler(logging.Handler):
|
|
|
88
83
|
level=record.levelname,
|
|
89
84
|
message=record.getMessage(),
|
|
90
85
|
timestamp=timestamp,
|
|
86
|
+
log_color=getattr(record, "log_color", None),
|
|
91
87
|
)
|
|
92
88
|
self.event_bus.publish(event)
|
|
93
89
|
except RuntimeError:
|
plain2code_state.py
CHANGED
|
@@ -48,7 +48,23 @@ class RunState:
|
|
|
48
48
|
self.rendered_functionalities += 1
|
|
49
49
|
|
|
50
50
|
def add_to_render_time(self):
|
|
51
|
-
|
|
51
|
+
now = time.monotonic()
|
|
52
|
+
self.render_time_accumulated += int(now - self.last_render_start_timestamp)
|
|
53
|
+
# Reset the segment start so get_live_render_time() does not re-count the
|
|
54
|
+
# span that was just banked (e.g. after a render completes).
|
|
55
|
+
self.last_render_start_timestamp = now
|
|
56
|
+
|
|
57
|
+
def get_live_render_time(self) -> int:
|
|
58
|
+
"""Render time so far in whole seconds, including the in-progress segment.
|
|
59
|
+
|
|
60
|
+
This is the single source of truth for elapsed render time, shared by the
|
|
61
|
+
log timestamps, the TUI usage line, and the post-render summary. Pause time
|
|
62
|
+
is excluded because ``add_to_render_time`` banks the elapsed span at the
|
|
63
|
+
start of a pause and ``set_last_render_start_timestamp`` restarts the
|
|
64
|
+
segment on resume. The value is only transiently inflated while the code
|
|
65
|
+
sits inside the pause loop itself, which no consumer samples.
|
|
66
|
+
"""
|
|
67
|
+
return self.render_time_accumulated + int(time.monotonic() - self.last_render_start_timestamp)
|
|
52
68
|
|
|
53
69
|
def set_last_render_start_timestamp(self):
|
|
54
70
|
self.last_render_start_timestamp = time.monotonic()
|