codeplain 0.3.7__py3-none-any.whl → 0.3.8__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.8.dist-info}/METADATA +4 -4
- {codeplain-0.3.7.dist-info → codeplain-0.3.8.dist-info}/RECORD +32 -27
- codeplain_REST_api.py +6 -2
- plain2code.py +1 -0
- plain2code_console.py +32 -21
- plain2code_events.py +1 -0
- plain2code_logger.py +3 -7
- plain2code_state.py +17 -1
- plain2code_utils.py +17 -13
- 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/render_functional_requirement.py +4 -3
- render_machine/render_context.py +5 -3
- render_machine/render_utils.py +14 -8
- system_config.py +35 -1
- tests/test_cli_output.py +2 -2
- tests/test_console_log_styles.py +112 -0
- tests/test_plain2code_state.py +36 -0
- tests/test_plain2code_utils.py +36 -0
- 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.8.dist-info}/WHEEL +0 -0
- {codeplain-0.3.7.dist-info → codeplain-0.3.8.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.7.dist-info → codeplain-0.3.8.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.8
|
|
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,6 +1,5 @@
|
|
|
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
|
|
@@ -10,25 +9,26 @@ memory_management.py,sha256=nHNudgaOqp5UGkIMi62TRCc2nUeFOM0w-I1FfxBFaQE,6120
|
|
|
10
9
|
module_renderer.py,sha256=DZCIo_QzvhFw8EqQTKeF3HUbvfumrV2O1sz-gUhWpIA,6752
|
|
11
10
|
partial_rendering.py,sha256=0ChOaGytogBUEGISpbWkbjT9Hqy8pnOkJdJXcitJDgk,12219
|
|
12
11
|
path_resolution.py,sha256=0tMqU0a1g4DYhTOp7QjLVSEkv_cgasb4NI1mK809dUM,2233
|
|
13
|
-
plain2code.py,sha256=
|
|
12
|
+
plain2code.py,sha256=YLwwzAvE0EWz51V9p8b_kbTltkPvhA2Q8J4JrYyMrAM,15327
|
|
14
13
|
plain2code_arguments.py,sha256=3tAA9Haw-j97zztjXdhi6LxFtURP0Tfg9J_rvJDoXmw,17685
|
|
15
|
-
plain2code_console.py,sha256=
|
|
16
|
-
plain2code_events.py,sha256=
|
|
14
|
+
plain2code_console.py,sha256=Ifs8tmfNX31UujQ5_0sk9AJcajMG_6qhF-6QZcfbcEE,5773
|
|
15
|
+
plain2code_events.py,sha256=rgDF5PDtNr_lEPvJmd1tIQXZrzwgzZW5b-_pykugqe4,1587
|
|
17
16
|
plain2code_exceptions.py,sha256=wkkEXw2k4LCd09PIH3_Hu0HyZvtXYjFaA653Sfey4FA,2287
|
|
18
|
-
plain2code_logger.py,sha256=
|
|
17
|
+
plain2code_logger.py,sha256=NRPLVIMnCHNVl6dII9gFoIzMdr8xa7icnL0T_Fx2XLw,4839
|
|
19
18
|
plain2code_nodes.py,sha256=IwjQqJxRSJKgP6eEnkYGy3VQHIyoazgkFxAEkxkjfJ8,4426
|
|
20
19
|
plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,2235
|
|
21
|
-
plain2code_state.py,sha256=
|
|
20
|
+
plain2code_state.py,sha256=uePQlC29Pbhm1yXfdCFebONYdy6jo13gTTmXSwC11cQ,3168
|
|
22
21
|
plain2code_telemetry.py,sha256=7yuTH7mQE_wGVW2H1oiVZ3UJg98ArB5HcUUm9isnv9g,4354
|
|
23
|
-
plain2code_utils.py,sha256=
|
|
22
|
+
plain2code_utils.py,sha256=unOxuqSQRpwG8YF6uoQhnZ3ZSTJRIUCPLdJ-1lRZR5M,1534
|
|
24
23
|
plain_file.py,sha256=k2rcq53q0kOf9_8egrZILpkrVkZdfJt7HU0gHBFskU4,35116
|
|
25
24
|
plain_modules.py,sha256=uvRPfxBJwstaWi5lgYKgtQdhhf0Naiy2mS41tLw_TjY,16186
|
|
26
25
|
plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
|
|
27
|
-
system_config.py,sha256=
|
|
26
|
+
system_config.py,sha256=VyE6MhXfi6agZvE02p_G4K8yCHkLh3eu890cBzPyDvs,2196
|
|
27
|
+
usage_summary.py,sha256=MA4WcVaAwz6IWCN4k_rgxVcwO4I157wN7BZlFi3h0R4,1282
|
|
28
28
|
cli_output/__init__.py,sha256=MBKl0YbVpWE-tBa3V5_aVbB7vbaqlIK2NsHTjRUZqis,284
|
|
29
29
|
cli_output/dry_run.py,sha256=6NKhNWPtD0CBK_QHSsSqIKLTzibTwxS9d8ltP9d1bgU,1473
|
|
30
|
-
cli_output/render_summary.py,sha256
|
|
31
|
-
cli_output/status.py,sha256=
|
|
30
|
+
cli_output/render_summary.py,sha256=-yRoI7ZZTS9IHyp1vd-sFleGXTVbxlrCkvuM5TX0Mp4,1168
|
|
31
|
+
cli_output/status.py,sha256=MvdocFXL1aA73NMp92gQb0Nd-f7-iw66XVUiHK33sEM,6194
|
|
32
32
|
config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
|
|
33
33
|
config/system_config.yaml,sha256=of5R9vukSohU9AlkGJOcAVQA1UJgxYmDy6islU6iFuE,508
|
|
34
34
|
docs/generate_cli.py,sha256=0FHVhICbM8g7ahdInTNih3hNKpty1wbV5rdGECMJj48,656
|
|
@@ -37,9 +37,9 @@ render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
37
37
|
render_machine/code_renderer.py,sha256=-vp_ltU7e3ssX9hbS8IY4ETSp-39JAuoJ3ZltKpdt1M,3959
|
|
38
38
|
render_machine/conformance_tests.py,sha256=MeEveEGJTClLGxWw04P4TR6U9kX9Qgz9qtxNrB9a8QI,7085
|
|
39
39
|
render_machine/implementation_code_helpers.py,sha256=LdHNLoKgVtrDaGH2APX8C9vjNjLBUhu2V7OAaJvm4rM,2009
|
|
40
|
-
render_machine/render_context.py,sha256=
|
|
40
|
+
render_machine/render_context.py,sha256=eoaO807LntIRYjLSpc3x7bvwBnFfzLlHmKkyKlIRrxs,25697
|
|
41
41
|
render_machine/render_types.py,sha256=cT3D0lmxQ3enIo0jGp3LEGCY2t3X1Q1_tqwcadzSAv0,8761
|
|
42
|
-
render_machine/render_utils.py,sha256=
|
|
42
|
+
render_machine/render_utils.py,sha256=Jb9NJ73yV1qCfGy9S5A9zcnz0FGVt3MHG8Ofk-9A-aI,9355
|
|
43
43
|
render_machine/state_machine_config.py,sha256=NnX5sBGkc6cNG8OOQ4PrJeJBCADRGZKJYQXV7ndR4bs,26789
|
|
44
44
|
render_machine/states.py,sha256=niFXqayyA_rezCrlzo0hYeHAruErcPzQVF-BeIBGnL8,1968
|
|
45
45
|
render_machine/triggers.py,sha256=VekNhHGR-u5JXIboqc_Eyqf73LNH7wezLK7r3WWtodk,1269
|
|
@@ -47,16 +47,16 @@ render_machine/actions/analyze_specification_ambiguity.py,sha256=SykZAozGvr1jySx
|
|
|
47
47
|
render_machine/actions/base_action.py,sha256=vVzMp-LanrZyq-jamHyWaQxpRl-pLqtTdtiEzUBhfyE,534
|
|
48
48
|
render_machine/actions/commit_conformance_tests_changes.py,sha256=08yxCPCpf5TlrzLPsEh5SJXVVvnLfwdNKSr7BlzlYxo,2589
|
|
49
49
|
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
|
|
50
|
+
render_machine/actions/create_dist.py,sha256=ykER7i2OvWAXjrlAZnkQHpiNl00vSMF8WDxNhlWKfsA,1088
|
|
51
|
+
render_machine/actions/exit_with_error.py,sha256=W6TMULHJcHEaXE4WUnlELEu0_l7IZy9HWaPkXyqg7ck,1279
|
|
52
52
|
render_machine/actions/finish_functional_requirement.py,sha256=usQgPSg6PHpBOR1RhK7lPz9zjY-2VJJDqi3T0nlqdLw,841
|
|
53
|
-
render_machine/actions/fix_conformance_test.py,sha256=
|
|
53
|
+
render_machine/actions/fix_conformance_test.py,sha256=EZLVjF4A8zSTmyqdgcLoz-2pLpeieq4M_Rb71pY3IKA,10095
|
|
54
54
|
render_machine/actions/fix_unit_tests.py,sha256=hvwvCJDk_-8fHMpkNFyeBSG1IshIcufDpymF00ulPXY,2301
|
|
55
55
|
render_machine/actions/prepare_repositories.py,sha256=eE-uU6H_K5IQMraVWSRi4TKk_XPrnA878T_JwtI9cWo,3410
|
|
56
56
|
render_machine/actions/prepare_testing_environment.py,sha256=kIsXgV6-jOlqp-04cimPgTQoHPrVIu0zW-H_Jj-jowE,2106
|
|
57
57
|
render_machine/actions/refactor_code.py,sha256=gyPsnqgHseL2bkENu4WeGCRyV0R18_GH3JOrPdoxKvg,2609
|
|
58
58
|
render_machine/actions/render_conformance_tests.py,sha256=5gOQn-G1T9UWEI8sDfALcVdArk0ln9DgUprOPjccS-U,9268
|
|
59
|
-
render_machine/actions/render_functional_requirement.py,sha256=
|
|
59
|
+
render_machine/actions/render_functional_requirement.py,sha256=53KzMVlcefoag1x73roCEQvQ7FbmH8FzhXMQau-s8tg,4655
|
|
60
60
|
render_machine/actions/run_conformance_tests.py,sha256=4sJTORl6YLFXQIdLMBQHDeJd2hztGyt3tsVvrCviBko,4008
|
|
61
61
|
render_machine/actions/run_unit_tests.py,sha256=A4WXtSzpCLDHYR8fQAbfxUf_E19GYflZSHZaSreoFPk,2101
|
|
62
62
|
render_machine/actions/summarize_conformance_tests.py,sha256=Vy1kRK8vyazK_BbsKuz1MLDN_TQWVxxzkwEEm-50byc,1639
|
|
@@ -68,7 +68,8 @@ tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
|
68
68
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
69
69
|
tests/test_arg_provenance.py,sha256=Vp-ZtNbTlSNOE8iiHOin3YI9sobbN0tRPUIfrYjxttQ,3459
|
|
70
70
|
tests/test_change_detection.py,sha256=jH8SEhndX400DNqIKmcYLzxHqq9WFdgmuvY3XPq-ZFc,13534
|
|
71
|
-
tests/test_cli_output.py,sha256=
|
|
71
|
+
tests/test_cli_output.py,sha256=iKS1Li4yhoIL0ezJ58o4oEf9V8TiKcQkvQLVtJpdM4c,18420
|
|
72
|
+
tests/test_console_log_styles.py,sha256=63DQWiwWNhe_rMP70bW9DAB6-EPaqU3Wpwyh8eR09pc,4080
|
|
72
73
|
tests/test_file_utils.py,sha256=R_ZIhrTtwlhHxgmbBtA8PFEU5-QB4Fi5skJ7PS2KzdU,3614
|
|
73
74
|
tests/test_folder_path_resolution.py,sha256=_ZvVFu5m2-AH8iQNn0fCYkA0flVxVwdh1oQiWcPNIxw,6611
|
|
74
75
|
tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
|
|
@@ -78,6 +79,8 @@ tests/test_logging_config_path_resolution.py,sha256=avz_ehRd18INwIXvAy1AwVzNNUz0
|
|
|
78
79
|
tests/test_partial_rendering.py,sha256=r0RUUd1pI4-2ZkRrM1j8_kUqv-70hrOGjuKBhZoRmH0,22758
|
|
79
80
|
tests/test_path_resolution.py,sha256=yQpd-Unq1lwNv0wkRPwVs5pDT8JmyyDgbpiTQsyt--Y,2615
|
|
80
81
|
tests/test_plain2code.py,sha256=DQYFc0JVSkMI8LZCNaQ__2Nph-DcZZW17rprJA54eyw,3851
|
|
82
|
+
tests/test_plain2code_state.py,sha256=cowPZS9oRbv9J49CE4P3Rc47Q5oQOby4bTf2OlnRlBI,1217
|
|
83
|
+
tests/test_plain2code_utils.py,sha256=-W8a7FquMJQ_pTdBoBF1XprzNL2G555QhdtXCy6BDyQ,1237
|
|
81
84
|
tests/test_plain_modules.py,sha256=S7sd4ZYNotXOgCyZzmBd714QFLih69nlZ3tV8WNtVPo,13766
|
|
82
85
|
tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
|
|
83
86
|
tests/test_plainfileparser.py,sha256=xd9P0KqZJlJPn3RKT510IQbfBotk6nQ9O8p9iLwJiQk,22815
|
|
@@ -86,20 +89,22 @@ tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
|
|
|
86
89
|
tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
|
|
87
90
|
tests/test_script_path_resolution.py,sha256=tiEqMevQRXr9YpfHN0xldOOQpLAUgnSqTzeZWkKHaUY,4156
|
|
88
91
|
tests/test_telemetry.py,sha256=hjm81aRQQZcZ0RB6aVbS3WO46_nIAii_csmbbtnUh74,7110
|
|
92
|
+
tests/test_tui_usage.py,sha256=sfiAGTpRrT3MRr0-8DH3qjuECSxjODVgwzrK_K3iPXs,7959
|
|
93
|
+
tests/test_usage_summary.py,sha256=Ps0kVzvmdZYeXrBR-cqkn3szTV5neYKs1PmmURngeIc,1079
|
|
89
94
|
tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
|
|
90
95
|
tests/e2e/test_hello_world_python.py,sha256=PQy4elzB7WvpiWLKa6_HFO0K8HA6_6hGLaAqRi0J3OU,1627
|
|
91
96
|
tests/e2e/test_hello_world_python_windows.py,sha256=kJQZIuzj8OEEtgBVxnzFvyQJ_wvP2oBu9L-E9geg8sE,2010
|
|
92
97
|
tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
|
|
93
|
-
tui/components.py,sha256=
|
|
98
|
+
tui/components.py,sha256=XkC4Aq6VyTnFnBQs8PEHwVdAOczhm0ebKN92kiYMbq0,25791
|
|
94
99
|
tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
|
|
95
|
-
tui/plain2code_tui.py,sha256=
|
|
100
|
+
tui/plain2code_tui.py,sha256=8F7ZYRpmTiWRdTeQ63LPf2LGrreYjIYXvf_9XNSPyHs,17104
|
|
96
101
|
tui/plain_module_render_choice_tui.py,sha256=DRl_xhUbUJsyOaCYt_uNDSLUAA3Ty9NYI9zFrVDwp9Y,11270
|
|
97
102
|
tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
98
103
|
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.
|
|
104
|
+
tui/styles.css,sha256=kiC7Og_2G1E7owoiZgV95-3_dSeQnsNj3qzaY04xkDg,6727
|
|
105
|
+
tui/widget_helpers.py,sha256=ToO9MV2bF2Rp-XMNcjIYclnB_qmgO5sLw4UtQ2CyTMg,6652
|
|
106
|
+
codeplain-0.3.8.dist-info/METADATA,sha256=nfj-9PV0EN0MS1W9ILf1GyOnSfcB-YSyEXW04UY9vHQ,6351
|
|
107
|
+
codeplain-0.3.8.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
108
|
+
codeplain-0.3.8.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
109
|
+
codeplain-0.3.8.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
|
|
110
|
+
codeplain-0.3.8.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
|
plain2code.py
CHANGED
|
@@ -286,6 +286,7 @@ def render( # noqa: C901
|
|
|
286
286
|
render_thread = threading.Thread(target=run_render, daemon=True)
|
|
287
287
|
app = Plain2CodeTUI(
|
|
288
288
|
event_bus=event_bus,
|
|
289
|
+
run_state=run_state,
|
|
289
290
|
on_ready=render_thread.start,
|
|
290
291
|
render_id=run_state.render_id,
|
|
291
292
|
unittests_script=args.unittests_script,
|
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()
|
plain2code_utils.py
CHANGED
|
@@ -16,19 +16,23 @@ def find_large_base64_blob(text: str) -> Optional[str]:
|
|
|
16
16
|
return match.group(0) if match else None
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def format_duration_hms(total_seconds:
|
|
20
|
-
"""Format a duration
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
if
|
|
29
|
-
return f"{
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
def format_duration_hms(total_seconds: float) -> str:
|
|
20
|
+
"""Format a whole-second duration compactly (e.g. ``10s``, ``5m 49s``, ``1h 2m``).
|
|
21
|
+
|
|
22
|
+
Fractional seconds are truncated. Durations under a minute render as ``{s}s``,
|
|
23
|
+
under an hour as ``{m}m {s}s``, and beyond as ``{h}h {m}m``.
|
|
24
|
+
"""
|
|
25
|
+
elapsed = int(total_seconds)
|
|
26
|
+
if elapsed < 0:
|
|
27
|
+
elapsed = 0
|
|
28
|
+
if elapsed < 60:
|
|
29
|
+
return f"{elapsed}s"
|
|
30
|
+
minutes = elapsed // 60
|
|
31
|
+
seconds = elapsed % 60
|
|
32
|
+
if minutes < 60:
|
|
33
|
+
return f"{minutes}m {seconds}s"
|
|
34
|
+
hours = minutes // 60
|
|
35
|
+
return f"{hours}h {minutes % 60}m"
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
AMBIGUITY_CAUSES = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
3
|
import file_utils
|
|
4
|
-
from plain2code_console import console
|
|
4
|
+
from plain2code_console import SUCCESS_COLOR, console
|
|
5
5
|
from render_machine.actions.base_action import BaseAction
|
|
6
6
|
from render_machine.render_context import RenderContext
|
|
7
7
|
|
|
@@ -21,6 +21,6 @@ class CreateDist(BaseAction):
|
|
|
21
21
|
render_context.conformance_tests.get_module_conformance_tests_folder(render_context.module_name),
|
|
22
22
|
render_context.conformance_tests_dest,
|
|
23
23
|
)
|
|
24
|
-
console.info(f"
|
|
24
|
+
console.info(f"✓ Render of module {render_context.module_name} completed successfully.", color=SUCCESS_COLOR)
|
|
25
25
|
|
|
26
26
|
return self.SUCCESSFUL_OUTCOME, None
|
|
@@ -20,7 +20,8 @@ class ExitWithError(BaseAction):
|
|
|
20
20
|
|
|
21
21
|
if render_context.frid_context is not None:
|
|
22
22
|
console.info(
|
|
23
|
-
f"To continue rendering from the last successfully rendered functionality,
|
|
23
|
+
f"To continue rendering from the last successfully rendered functionality, "
|
|
24
|
+
f"provide the --render-from {render_context.frid_context.frid} flag."
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
if render_context.run_state.render_id is not None:
|
|
@@ -4,7 +4,7 @@ import diff_utils
|
|
|
4
4
|
import file_utils
|
|
5
5
|
import plain_spec
|
|
6
6
|
from memory_management import MemoryManager
|
|
7
|
-
from plain2code_console import console
|
|
7
|
+
from plain2code_console import RETRY_COLOR, console
|
|
8
8
|
from plain2code_exceptions import InternalClientError
|
|
9
9
|
from render_machine.actions.base_action import BaseAction
|
|
10
10
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
@@ -144,7 +144,9 @@ class FixConformanceTest(BaseAction):
|
|
|
144
144
|
render_context.conformance_tests_running_context.conflicting_module_name = current_testing_module_name
|
|
145
145
|
render_context.conformance_tests_running_context.conflicting_frid = current_testing_frid
|
|
146
146
|
console.info(
|
|
147
|
-
f"Potential conflicting functionalities detected while fixing conformance tests
|
|
147
|
+
f"↻ Potential conflicting functionalities detected while fixing conformance tests "
|
|
148
|
+
f"for functionality {current_testing_frid} in module {current_testing_module_name}.",
|
|
149
|
+
color=RETRY_COLOR,
|
|
148
150
|
)
|
|
149
151
|
|
|
150
152
|
if issue_reason_code == self.ISSUE_REASON_CODE_CONFORMANCE_TESTS:
|
|
@@ -3,7 +3,7 @@ from typing import Any
|
|
|
3
3
|
import file_utils
|
|
4
4
|
import render_machine.render_utils as render_utils
|
|
5
5
|
from memory_management import MemoryManager
|
|
6
|
-
from plain2code_console import console
|
|
6
|
+
from plain2code_console import RETRY_COLOR, console
|
|
7
7
|
from plain2code_exceptions import FunctionalRequirementTooComplex
|
|
8
8
|
from render_machine.actions.base_action import BaseAction
|
|
9
9
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
@@ -28,8 +28,9 @@ class RenderFunctionalRequirement(BaseAction):
|
|
|
28
28
|
|
|
29
29
|
if render_context.frid_context.functional_requirement_render_attempts > 1:
|
|
30
30
|
console.info(
|
|
31
|
-
f"Unittests could not be fixed after rendering the functionality. "
|
|
32
|
-
f"Restarting rendering functionality {render_context.frid_context.frid} from scratch."
|
|
31
|
+
f"↻ Unittests could not be fixed after rendering the functionality. "
|
|
32
|
+
f"Restarting rendering functionality {render_context.frid_context.frid} from scratch.",
|
|
33
|
+
color=RETRY_COLOR,
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
render_utils.revert_changes_for_frid(render_context)
|
render_machine/render_context.py
CHANGED
|
@@ -7,7 +7,7 @@ import git_utils
|
|
|
7
7
|
import plain_spec
|
|
8
8
|
from codeplain_REST_api import CodeplainAPI
|
|
9
9
|
from event_bus import EventBus
|
|
10
|
-
from plain2code_console import console
|
|
10
|
+
from plain2code_console import RETRY_COLOR, console
|
|
11
11
|
from plain2code_events import RenderContextSnapshot
|
|
12
12
|
from plain2code_state import RunState
|
|
13
13
|
from plain_modules import PlainModule
|
|
@@ -283,9 +283,11 @@ class RenderContext:
|
|
|
283
283
|
self.dispatch_error(error_msg)
|
|
284
284
|
else:
|
|
285
285
|
console.info(
|
|
286
|
-
f"Failed to adjust the unit tests after implementation code was updated while fixing the
|
|
286
|
+
f"↻ Failed to adjust the unit tests after implementation code was updated while fixing the "
|
|
287
|
+
f"conformance tests for functionality {self.frid_context.frid}. "
|
|
288
|
+
f"Restarting rendering the functionality {self.frid_context.frid} from scratch.",
|
|
289
|
+
color=RETRY_COLOR,
|
|
287
290
|
)
|
|
288
|
-
console.info(f"Restarting rendering the functionality {self.frid_context.frid} from scratch.")
|
|
289
291
|
self.machine.dispatch(triggers.RESTART_FRID_PROCESSING)
|
|
290
292
|
|
|
291
293
|
def _on_unit_test_limit_exceeded_in_refactoring(self):
|
render_machine/render_utils.py
CHANGED
|
@@ -14,7 +14,7 @@ if sys.platform == "linux":
|
|
|
14
14
|
import file_utils
|
|
15
15
|
import git_utils
|
|
16
16
|
import plain_spec
|
|
17
|
-
from plain2code_console import console
|
|
17
|
+
from plain2code_console import MUTED_COLOR, RETRY_COLOR, SUCCESS_COLOR, console
|
|
18
18
|
from plain2code_exceptions import RenderCancelledError
|
|
19
19
|
|
|
20
20
|
SCRIPT_EXECUTION_TIMEOUT = 120
|
|
@@ -178,24 +178,30 @@ def execute_script( # noqa: C901
|
|
|
178
178
|
temp_file.write(f"{script_type} script {script} successfully passed.\n")
|
|
179
179
|
temp_file.write(f"{script_type} script execution time: {elapsed_time:.2f} seconds.\n")
|
|
180
180
|
|
|
181
|
-
console.debug(f"
|
|
181
|
+
console.debug(f"{script_type} script output stored in: {temp_file_path.strip()}", color=MUTED_COLOR)
|
|
182
182
|
|
|
183
183
|
if proc.returncode != 0:
|
|
184
184
|
if frid is not None:
|
|
185
|
-
console.
|
|
186
|
-
f"The {script_type} script for functionality ID {frid} of module {module} has failed.
|
|
185
|
+
console.info(
|
|
186
|
+
f"↻ The {script_type} script for functionality ID {frid} of module {module} has failed. "
|
|
187
|
+
f"Initiating the patching mode to automatically correct the discrepancies.",
|
|
188
|
+
color=RETRY_COLOR,
|
|
187
189
|
)
|
|
188
190
|
else:
|
|
189
|
-
console.
|
|
190
|
-
f"The {script_type} script has failed.
|
|
191
|
+
console.info(
|
|
192
|
+
f"↻ The {script_type} script has failed. "
|
|
193
|
+
f"Initiating the patching mode to automatically correct the discrepancies.",
|
|
194
|
+
color=RETRY_COLOR,
|
|
191
195
|
)
|
|
192
196
|
else:
|
|
193
197
|
if frid is not None:
|
|
194
198
|
console.info(
|
|
195
|
-
f"
|
|
199
|
+
f"✓ The {script_type} script for functionality ID {frid} of module {module} "
|
|
200
|
+
f"has passed successfully.",
|
|
201
|
+
color=SUCCESS_COLOR,
|
|
196
202
|
)
|
|
197
203
|
else:
|
|
198
|
-
console.info(f"
|
|
204
|
+
console.info(f"✓ All {script_type} scripts have passed successfully.", color=SUCCESS_COLOR)
|
|
199
205
|
|
|
200
206
|
return proc.returncode, sanitized_script_output, temp_file_path
|
|
201
207
|
|
system_config.py
CHANGED
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
import importlib.resources
|
|
2
|
+
import os
|
|
2
3
|
import sys
|
|
3
4
|
|
|
4
5
|
import yaml
|
|
5
6
|
|
|
6
|
-
from _version import __version__
|
|
7
7
|
from plain2code_console import console
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _resolve_version() -> str:
|
|
11
|
+
"""Resolve the client version.
|
|
12
|
+
|
|
13
|
+
For an installed package the version is read from its metadata (hatch-vcs
|
|
14
|
+
bakes it in from the git tag at build time). When running from a source
|
|
15
|
+
checkout that hasn't been installed, fall back to the nearest git tag.
|
|
16
|
+
"""
|
|
17
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
return version("codeplain")
|
|
21
|
+
except PackageNotFoundError:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import git
|
|
26
|
+
|
|
27
|
+
# Anchor to this source file's own location so we inspect the
|
|
28
|
+
# codeplain checkout's repo, not the caller's working directory
|
|
29
|
+
# (codeplain may be run from anywhere).
|
|
30
|
+
source_dir = os.path.dirname(os.path.abspath(__file__))
|
|
31
|
+
repo = git.Repo(source_dir, search_parent_directories=True)
|
|
32
|
+
|
|
33
|
+
# Highest version tag, regardless of branch ancestry (a dev run may sit
|
|
34
|
+
# on a feature branch that doesn't descend from the latest release tag).
|
|
35
|
+
latest_tag = repo.git.tag("--list", "--sort=-v:refname").splitlines()[0]
|
|
36
|
+
return latest_tag.lstrip("v")
|
|
37
|
+
except Exception:
|
|
38
|
+
return "0.0.0.dev0"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__version__ = _resolve_version()
|
|
42
|
+
|
|
43
|
+
|
|
10
44
|
class SystemConfig:
|
|
11
45
|
"""Manages system-level configuration including requirements and error messages."""
|
|
12
46
|
|
tests/test_cli_output.py
CHANGED
|
@@ -79,14 +79,14 @@ class TestDisplayCreditLine:
|
|
|
79
79
|
"type": "pro",
|
|
80
80
|
"total": 1000,
|
|
81
81
|
"remaining": 200,
|
|
82
|
-
"period_end": "
|
|
82
|
+
"period_end": "2028-07-15T00:00:00+00:00",
|
|
83
83
|
}
|
|
84
84
|
_display_credit_line(plan_credits)
|
|
85
85
|
|
|
86
86
|
call_args = mock_console.print.call_args[0][0]
|
|
87
87
|
assert "PRO plan" in call_args
|
|
88
88
|
assert "200 of 1000 remaining" in call_args
|
|
89
|
-
assert "expires Jul 15,
|
|
89
|
+
assert "expires Jul 15, 2028" in call_args
|
|
90
90
|
|
|
91
91
|
@patch("cli_output.status.console")
|
|
92
92
|
def test_display_expired_credits(self, mock_console):
|