codeplain 0.2.4__py3-none-any.whl → 0.2.5__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.
- {codeplain-0.2.4.dist-info → codeplain-0.2.5.dist-info}/METADATA +3 -3
- {codeplain-0.2.4.dist-info → codeplain-0.2.5.dist-info}/RECORD +41 -41
- codeplain_REST_api.py +57 -37
- config/system_config.yaml +1 -16
- file_utils.py +5 -4
- git_utils.py +42 -7
- memory_management.py +1 -1
- module_renderer.py +114 -0
- plain2code.py +87 -19
- plain2code_console.py +3 -5
- plain2code_exceptions.py +14 -4
- plain2code_logger.py +11 -5
- plain2code_utils.py +7 -5
- plain_file.py +1 -1
- plain_spec.py +22 -2
- render_machine/actions/create_dist.py +1 -1
- render_machine/actions/exit_with_error.py +1 -1
- render_machine/actions/prepare_testing_environment.py +1 -1
- render_machine/actions/render_conformance_tests.py +2 -4
- render_machine/actions/render_functional_requirement.py +6 -6
- render_machine/actions/run_conformance_tests.py +3 -2
- render_machine/actions/run_unit_tests.py +1 -1
- render_machine/render_context.py +3 -3
- render_machine/render_utils.py +14 -6
- standard_template_library/golang-console-app-template.plain +2 -2
- standard_template_library/python-console-app-template.plain +2 -2
- standard_template_library/typescript-react-app-template.plain +2 -2
- system_config.py +3 -11
- tests/test_imports.py +2 -2
- tests/test_plainfile.py +1 -1
- tests/test_plainfileparser.py +10 -10
- tests/test_plainspec.py +2 -2
- tui/components.py +311 -103
- tui/plain2code_tui.py +101 -52
- tui/state_handlers.py +94 -47
- tui/styles.css +240 -52
- tui/widget_helpers.py +43 -47
- {codeplain-0.2.4.dist-info → codeplain-0.2.5.dist-info}/WHEEL +0 -0
- {codeplain-0.2.4.dist-info → codeplain-0.2.5.dist-info}/entry_points.txt +0 -0
- {codeplain-0.2.4.dist-info → codeplain-0.2.5.dist-info}/licenses/LICENSE +0 -0
- /spinner.py → /tui/spinner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -14,13 +14,13 @@ Requires-Dist: networkx==3.6.1
|
|
|
14
14
|
Requires-Dist: python-frontmatter==1.1.0
|
|
15
15
|
Requires-Dist: python-liquid2==0.3.0
|
|
16
16
|
Requires-Dist: pyyaml==6.0.2
|
|
17
|
-
Requires-Dist: requests==2.32.
|
|
17
|
+
Requires-Dist: requests==2.32.4
|
|
18
18
|
Requires-Dist: rich==14.2.0
|
|
19
19
|
Requires-Dist: textual==1.0.0
|
|
20
20
|
Requires-Dist: tiktoken==0.12.0
|
|
21
21
|
Requires-Dist: transitions==0.9.3
|
|
22
22
|
Provides-Extra: dev
|
|
23
|
-
Requires-Dist: black==24.
|
|
23
|
+
Requires-Dist: black==24.3.0; extra == 'dev'
|
|
24
24
|
Requires-Dist: flake8==7.0.0; extra == 'dev'
|
|
25
25
|
Requires-Dist: isort==5.13.2; extra == 'dev'
|
|
26
26
|
Requires-Dist: mypy==1.11.2; extra == 'dev'
|
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
codeplain_REST_api.py,sha256=
|
|
1
|
+
codeplain_REST_api.py,sha256=54OGXuuh4b6mHLgs9n902My4CaRiBk0OJmK3ZJU5wbI,18163
|
|
2
2
|
concept_utils.py,sha256=vwSY4-FmxyqaDBxhntYzqG8t9pXvAWwiULP0DYQI32o,7905
|
|
3
3
|
diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
|
|
4
4
|
event_bus.py,sha256=sduIR3bgIbxAbLhwKd8Gx9YN9gzaeqy9-mNupS04aKw,1759
|
|
5
|
-
file_utils.py,sha256=
|
|
6
|
-
git_utils.py,sha256=
|
|
5
|
+
file_utils.py,sha256=AYs5JM-DidAMwG0cTsSeRGK4tsKl0mqv3OIPi_5cfZ0,11614
|
|
6
|
+
git_utils.py,sha256=JjUTE5TPYq4-b2I84X7L2zH0r3KCq1Xcg_hZo_PnDtI,13660
|
|
7
7
|
hash_key.py,sha256=lMKgKpOhPeC4UpFf9e7eCNK20FEaDsXTCJ8j3eCWidE,837
|
|
8
|
-
memory_management.py,sha256=
|
|
9
|
-
module_renderer.py,sha256=
|
|
10
|
-
plain2code.py,sha256=
|
|
8
|
+
memory_management.py,sha256=rcl0C_vLKaCNzv1_LuMmHPzkJZpOSinHGEg13j2ceZc,4358
|
|
9
|
+
module_renderer.py,sha256=7Imv8RXOTVbEDnlVELwFYzfMx44vEb4HKsIRDjf3jFI,11678
|
|
10
|
+
plain2code.py,sha256=bhtzCHED6xYcue-_WuX46FWzjAoDDw0c_Eximrp1dFM,12306
|
|
11
11
|
plain2code_arguments.py,sha256=PdQjd3orklrtlEyz7-Qdz_m2SvjZtZp6dmbkqXgglSU,11826
|
|
12
|
-
plain2code_console.py,sha256
|
|
12
|
+
plain2code_console.py,sha256=HNL7XVwRE9kCjsiVB1BVgckgqv5YevbHc4Pl3hDJAX4,4056
|
|
13
13
|
plain2code_events.py,sha256=5fPhpRzgJ02xWh0MXqrMv9A4curr2UOE68Y1uKlAcqQ,1347
|
|
14
|
-
plain2code_exceptions.py,sha256=
|
|
15
|
-
plain2code_logger.py,sha256=
|
|
14
|
+
plain2code_exceptions.py,sha256=Vuv6amo9lgc4hy2pNxhOBUuxhP7BkjGAaovj0xVu7s8,1135
|
|
15
|
+
plain2code_logger.py,sha256=I9AchbS34QY_8lvqUHe_TSJLfRZqfdi9rtV_OpJ0dss,4416
|
|
16
16
|
plain2code_nodes.py,sha256=Om624mfb5MB1Z09c0Zqtb9G1gGkUY9x-v2hzAf3A2gw,3818
|
|
17
17
|
plain2code_read_config.py,sha256=QnpbW_Bi9LzDIOubM5x7fVXH1HgiFL3-5Oa_O1EUoE4,2449
|
|
18
18
|
plain2code_state.py,sha256=InLXdytMXnm9YC4o8MSTbtB1TmqYYUs6rASGBlyA_YY,1274
|
|
19
|
-
plain2code_utils.py,sha256=
|
|
20
|
-
plain_file.py,sha256=
|
|
19
|
+
plain2code_utils.py,sha256=CubIc4S_cyz92OZJkiUSTfLejesNO3fqVrflOLPI4qM,1772
|
|
20
|
+
plain_file.py,sha256=UxO8_DC_eqpn_joxdbJfF7wS5T1Kdd1JFYyI908vhsM,28434
|
|
21
21
|
plain_modules.py,sha256=6gteJeiK-WvUZDrIt_kJK0MFiptq63rEVpYvV5yYFXc,4881
|
|
22
|
-
plain_spec.py,sha256=
|
|
23
|
-
|
|
24
|
-
system_config.py,sha256=mgHLn-CRHLO9Y9vKyI_eFBreY_YhFad-ctZgBYp-rIg,1777
|
|
22
|
+
plain_spec.py,sha256=WY2cStFdwRLL8itlOwEOfX5ixFsFsbSKi_iqb9kgDfc,14078
|
|
23
|
+
system_config.py,sha256=_m75WJEo8_kG6Q8keiLp0dGW0fmE_X8_4aoVhg58j6k,1460
|
|
25
24
|
config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
|
|
26
|
-
config/system_config.yaml,sha256=
|
|
25
|
+
config/system_config.yaml,sha256=LLxWyAP7oOzajc6mMue-UaRO7qjur0TpzAv2rLOix7I,534
|
|
27
26
|
docs/generate_cli.py,sha256=xJmiihtdgqEDBYt_wBsRbniZvIfq7Q6tjAsi3SjoMJg,531
|
|
28
27
|
examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py,sha256=dwTowrHiVKKbrDv21v8xJC30Q57AXZkQasdGOO5JsBE,470
|
|
29
28
|
render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -31,9 +30,9 @@ render_machine/code_renderer.py,sha256=jOoYivkZzCuGG947YlNPlvD1Dpd2xTWVK8pwfF_Oq
|
|
|
31
30
|
render_machine/conformance_test_helpers.py,sha256=6Ru7Dh24SLNIKWfz_sP5hE_EmWpvk6nfgN1T1yaCZus,2887
|
|
32
31
|
render_machine/conformance_tests.py,sha256=M0dMCawkUljMLwnf-9BLzjyKKJjfxmxJ4wUFyJX0hHU,7155
|
|
33
32
|
render_machine/implementation_code_helpers.py,sha256=KctilT0x07niph0IWgKwP7ACXw8KJyErhJsl1iC3LQg,960
|
|
34
|
-
render_machine/render_context.py,sha256=
|
|
33
|
+
render_machine/render_context.py,sha256=LMgIN3pVQs1aSccyU6T3fXi-ANAT-K51r3t52UmO2iM,21498
|
|
35
34
|
render_machine/render_types.py,sha256=Y0MTzlEEPyqEF3NH-SGe5tL5TeCjYOCWhQ1aqBZdy-Q,7187
|
|
36
|
-
render_machine/render_utils.py,sha256=
|
|
35
|
+
render_machine/render_utils.py,sha256=j6UAEJnBP_khpub-ix98Xiq69i0X6PLqaDVihfdgjaQ,4999
|
|
37
36
|
render_machine/state_machine_config.py,sha256=sd-P9kdvBYlj0R6aT1RZLFquIwAcbEBNL3aZG-Wo4hw,27988
|
|
38
37
|
render_machine/states.py,sha256=niFXqayyA_rezCrlzo0hYeHAruErcPzQVF-BeIBGnL8,1968
|
|
39
38
|
render_machine/triggers.py,sha256=GarwIYPY6ZZzVdRF7yXLB54tofr08NpFZfp5DH6AOuI,1331
|
|
@@ -41,41 +40,42 @@ render_machine/actions/analyze_specification_ambiguity.py,sha256=okEmM03WVGnDR4c
|
|
|
41
40
|
render_machine/actions/base_action.py,sha256=vVzMp-LanrZyq-jamHyWaQxpRl-pLqtTdtiEzUBhfyE,534
|
|
42
41
|
render_machine/actions/commit_conformance_tests_changes.py,sha256=08yxCPCpf5TlrzLPsEh5SJXVVvnLfwdNKSr7BlzlYxo,2589
|
|
43
42
|
render_machine/actions/commit_implementation_code_changes.py,sha256=yi51oAArLCgWcmFbynes-BK2IGnZwA7wygrTJGoL8dY,826
|
|
44
|
-
render_machine/actions/create_dist.py,sha256=
|
|
45
|
-
render_machine/actions/exit_with_error.py,sha256=
|
|
43
|
+
render_machine/actions/create_dist.py,sha256=du1Ur5rmES6OEg_ZXjJEDptEuN_FYFhRCFoTrNDeALM,1065
|
|
44
|
+
render_machine/actions/exit_with_error.py,sha256=RUslIaZC6y_1Gx65QKd7CxwnOMo7xUkH1mKeuQE1X2M,1037
|
|
46
45
|
render_machine/actions/finish_functional_requirement.py,sha256=g279oDxfz7BoRxoJT6Gsv5reawed1lfLZw_7fn0mZ0g,687
|
|
47
46
|
render_machine/actions/fix_conformance_test.py,sha256=YsKPDlheGtBmzh0ac9ugsI8JZQYAZW5CHevZSsatlKI,7326
|
|
48
47
|
render_machine/actions/fix_unit_tests.py,sha256=wrj5c8flhln9wAZUQ-hELI3dRbLNl1HZqImNDcLE-OE,2558
|
|
49
48
|
render_machine/actions/prepare_repositories.py,sha256=1jLGwbJS7jJkhjm-1p2oNv_9n1pD105S1gCCnMNCyrg,3160
|
|
50
|
-
render_machine/actions/prepare_testing_environment.py,sha256=
|
|
49
|
+
render_machine/actions/prepare_testing_environment.py,sha256=VN4CSFTM6zolO7p6pM4afgGY0Rocka6JWSTQLevxLlI,1844
|
|
51
50
|
render_machine/actions/refactor_code.py,sha256=2cZViWb4Dj6t7dCN2fo9qniIIrn_VE22dsSORjmr_oA,2112
|
|
52
|
-
render_machine/actions/render_conformance_tests.py,sha256=
|
|
53
|
-
render_machine/actions/render_functional_requirement.py,sha256=
|
|
54
|
-
render_machine/actions/run_conformance_tests.py,sha256=
|
|
55
|
-
render_machine/actions/run_unit_tests.py,sha256=
|
|
51
|
+
render_machine/actions/render_conformance_tests.py,sha256=gQaYImSAD6fK8FhfDMQcKH1Uy9_Qq6jsj5ZEKHLmy9c,8798
|
|
52
|
+
render_machine/actions/render_functional_requirement.py,sha256=lk9SquVSH_EwvtXPqyEfoirGwisQf5eHdBdV-NG2eoY,4120
|
|
53
|
+
render_machine/actions/run_conformance_tests.py,sha256=ttc1-1jQUBhCGw6mxoMLkpAbVd3w2tlERMnWQcHpPbA,3488
|
|
54
|
+
render_machine/actions/run_unit_tests.py,sha256=68huFbL_RExMGw5XaTDKwu18uQyEgo_fxREfO5aYyBI,2024
|
|
56
55
|
render_machine/actions/summarize_conformance_tests.py,sha256=BbBMJX3HkrZF_BlyNFw6prPgF9NLySapoLeGQ6_e3xU,1867
|
|
57
56
|
standard_template_library/__init__.py,sha256=LtsHl0c3VV46DUInIqyOw4KKVoxKEMOx8dAZpMXtAXc,43
|
|
58
|
-
standard_template_library/golang-console-app-template.plain,sha256=
|
|
59
|
-
standard_template_library/python-console-app-template.plain,sha256=
|
|
57
|
+
standard_template_library/golang-console-app-template.plain,sha256=JOusEjCj7jagnXUzaI-SdIr3hj1wTo2uShK017aNtVM,949
|
|
58
|
+
standard_template_library/python-console-app-template.plain,sha256=I5Zd28GInw3vLa9Ub9qnQjjiCuslsycguNcsBEa19Wc,1008
|
|
60
59
|
standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOzWnDHdVpFj-XBTq6s4_2dy7LKV3lVkCzv8i4,114
|
|
61
|
-
standard_template_library/typescript-react-app-template.plain,sha256=
|
|
60
|
+
standard_template_library/typescript-react-app-template.plain,sha256=Rh8THR6ADSBZvw4ZF2dlMuaKU7xYTNaQ688R8T61Y44,383
|
|
62
61
|
tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
63
62
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
64
63
|
tests/test_git_utils.py,sha256=Knc4R2ZkHYVj9iPFKhb0_ewaad_mxPrakOn6gpziHMc,13138
|
|
65
|
-
tests/test_imports.py,sha256=
|
|
66
|
-
tests/test_plainfile.py,sha256=
|
|
67
|
-
tests/test_plainfileparser.py,sha256=
|
|
68
|
-
tests/test_plainspec.py,sha256=
|
|
64
|
+
tests/test_imports.py,sha256=EWYLUxGunUUdsG9iCiY_tts6T7DIVdhdB_MZlB1LsmU,2081
|
|
65
|
+
tests/test_plainfile.py,sha256=H0asjkBjdqAgwHHZJTaGYm0BG97ah6yirLk-yaHBzwM,11090
|
|
66
|
+
tests/test_plainfileparser.py,sha256=Pq52n5NzJP_h56BRk7OofRwry0aAW1Al86U3VEkte9U,18724
|
|
67
|
+
tests/test_plainspec.py,sha256=kfTyMlZ8eiIoIGqvEN6iwqvFLckx14S_zZRUuU1T2ko,2440
|
|
69
68
|
tests/test_requires.py,sha256=BwlZeLXIs4m-FGnjLjO4vHkxfEX_QgjYoomb3oIu7Vs,1236
|
|
70
69
|
tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
|
|
71
|
-
tui/components.py,sha256=
|
|
70
|
+
tui/components.py,sha256=A-Z4NrNl5B8ZmeoeKm7jMTFU18B8vP7Roa-Q6r84CcE,21797
|
|
72
71
|
tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
|
|
73
|
-
tui/plain2code_tui.py,sha256=
|
|
74
|
-
tui/
|
|
75
|
-
tui/
|
|
76
|
-
tui/
|
|
77
|
-
|
|
78
|
-
codeplain-0.2.
|
|
79
|
-
codeplain-0.2.
|
|
80
|
-
codeplain-0.2.
|
|
81
|
-
codeplain-0.2.
|
|
72
|
+
tui/plain2code_tui.py,sha256=mj6fzksBYCqqltXL6z2VXh-u7Y6NMOmp9lsnNhHEbGc,14318
|
|
73
|
+
tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
74
|
+
tui/state_handlers.py,sha256=rjufJdDCS8IYy_0PmA_eNBdHe4iHAjUunaAM_mnJJY8,15155
|
|
75
|
+
tui/styles.css,sha256=BN5A1dm3AZi9Tjyry6_B2rctcTeP5YTP9ltqUuFUSWM,6083
|
|
76
|
+
tui/widget_helpers.py,sha256=E-ZkBx6z6x_d6SMCcWc7I63jI_1x90YwI3v7uOfVsY8,5248
|
|
77
|
+
codeplain-0.2.5.dist-info/METADATA,sha256=Sh1o1WSwyRAQ9RyJpVKL9MYJqrPdMyxPCfxXiW9RClQ,4278
|
|
78
|
+
codeplain-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
79
|
+
codeplain-0.2.5.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
80
|
+
codeplain-0.2.5.dist-info/licenses/LICENSE,sha256=wsFi5dpbJurnRNfBj8q2RCcF3ryrmdRIfxc3lPcmc4c,1069
|
|
81
|
+
codeplain-0.2.5.dist-info/RECORD,,
|
codeplain_REST_api.py
CHANGED
|
@@ -2,7 +2,6 @@ import time
|
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
|
-
from requests.exceptions import ConnectionError, RequestException, Timeout
|
|
6
5
|
|
|
7
6
|
import plain2code_exceptions
|
|
8
7
|
from plain2code_state import RunState
|
|
@@ -10,6 +9,22 @@ from plain2code_state import RunState
|
|
|
10
9
|
MAX_RETRIES = 4
|
|
11
10
|
RETRY_DELAY = 3
|
|
12
11
|
|
|
12
|
+
# TODO: Handle connection errors
|
|
13
|
+
RETRY_ERROR_CODES = [
|
|
14
|
+
"LLMInternalError",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# Mapping from API error codes to exception classes
|
|
18
|
+
ERROR_CODE_EXCEPTIONS = {
|
|
19
|
+
"FunctionalRequirementTooComplex": plain2code_exceptions.FunctionalRequirementTooComplex,
|
|
20
|
+
"ConflictingRequirements": plain2code_exceptions.ConflictingRequirements,
|
|
21
|
+
"CreditBalanceTooLow": plain2code_exceptions.CreditBalanceTooLow,
|
|
22
|
+
"LLMInternalError": plain2code_exceptions.LLMInternalError,
|
|
23
|
+
"MissingResource": plain2code_exceptions.MissingResource,
|
|
24
|
+
"PlainSyntaxError": plain2code_exceptions.PlainSyntaxError,
|
|
25
|
+
"InternalServerError": plain2code_exceptions.InternalServerError,
|
|
26
|
+
}
|
|
27
|
+
|
|
13
28
|
|
|
14
29
|
class CodeplainAPI:
|
|
15
30
|
|
|
@@ -29,12 +44,31 @@ class CodeplainAPI:
|
|
|
29
44
|
run_state.increment_call_count()
|
|
30
45
|
payload["render_state"] = run_state.to_dict()
|
|
31
46
|
|
|
32
|
-
def
|
|
47
|
+
def _raise_for_error_code(self, response_json):
|
|
48
|
+
"""Raise appropriate exception based on error code in response."""
|
|
49
|
+
error_code = response_json.get("error_code")
|
|
50
|
+
if error_code not in ERROR_CODE_EXCEPTIONS:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
exception_class = ERROR_CODE_EXCEPTIONS[error_code]
|
|
54
|
+
message = response_json.get("message", "")
|
|
55
|
+
|
|
56
|
+
# FunctionalRequirementTooComplex has an extra parameter
|
|
57
|
+
if error_code == "FunctionalRequirementTooComplex":
|
|
58
|
+
raise exception_class(message, response_json.get("proposed_breakdown"))
|
|
59
|
+
|
|
60
|
+
raise exception_class(message)
|
|
61
|
+
|
|
62
|
+
def post_request(
|
|
63
|
+
self, endpoint_url, headers, payload, run_state: Optional[RunState], num_retries: int = MAX_RETRIES
|
|
64
|
+
):
|
|
33
65
|
if run_state is not None:
|
|
34
66
|
self._extend_payload_with_run_state(payload, run_state)
|
|
35
67
|
|
|
36
68
|
retry_delay = RETRY_DELAY
|
|
37
|
-
|
|
69
|
+
response_json = None
|
|
70
|
+
|
|
71
|
+
for attempt in range(num_retries + 1):
|
|
38
72
|
try:
|
|
39
73
|
response = requests.post(endpoint_url, headers=headers, json=payload)
|
|
40
74
|
|
|
@@ -45,47 +79,33 @@ class CodeplainAPI:
|
|
|
45
79
|
raise
|
|
46
80
|
|
|
47
81
|
if response.status_code == requests.codes.bad_request and "error_code" in response_json:
|
|
48
|
-
|
|
49
|
-
raise plain2code_exceptions.FunctionalRequirementTooComplex(
|
|
50
|
-
response_json["message"], response_json.get("proposed_breakdown")
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
if response_json["error_code"] == "ConflictingRequirements":
|
|
54
|
-
raise plain2code_exceptions.ConflictingRequirements(response_json["message"])
|
|
55
|
-
|
|
56
|
-
if response_json["error_code"] == "CreditBalanceTooLow":
|
|
57
|
-
raise plain2code_exceptions.CreditBalanceTooLow(response_json["message"])
|
|
58
|
-
|
|
59
|
-
if response_json["error_code"] == "LLMInternalError":
|
|
60
|
-
raise plain2code_exceptions.LLMInternalError(response_json["message"])
|
|
61
|
-
|
|
62
|
-
if response_json["error_code"] == "MissingResource":
|
|
63
|
-
raise plain2code_exceptions.MissingResource(response_json["message"])
|
|
64
|
-
|
|
65
|
-
if response_json["error_code"] == "PlainSyntaxError":
|
|
66
|
-
raise plain2code_exceptions.PlainSyntaxError(response_json["message"])
|
|
67
|
-
|
|
68
|
-
if response_json["error_code"] == "NoRenderFound":
|
|
69
|
-
raise plain2code_exceptions.NoRenderFound(response_json["message"])
|
|
70
|
-
|
|
71
|
-
if response_json["error_code"] == "MultipleRendersFound":
|
|
72
|
-
raise plain2code_exceptions.MultipleRendersFound(response_json["message"])
|
|
82
|
+
self._raise_for_error_code(response_json)
|
|
73
83
|
|
|
74
84
|
response.raise_for_status()
|
|
75
85
|
return response_json
|
|
76
86
|
|
|
77
|
-
except
|
|
78
|
-
if
|
|
79
|
-
|
|
87
|
+
except Exception as e:
|
|
88
|
+
if response_json is not None and "error_code" in response_json:
|
|
89
|
+
if response_json["error_code"] not in RETRY_ERROR_CODES:
|
|
90
|
+
raise e
|
|
91
|
+
|
|
92
|
+
if attempt < num_retries:
|
|
93
|
+
self.console.info(f"Error on attempt {attempt + 1}/{num_retries + 1}: {e}")
|
|
80
94
|
self.console.info(f"Retrying in {retry_delay} seconds...")
|
|
81
95
|
time.sleep(retry_delay)
|
|
82
|
-
# Exponential backoff
|
|
83
|
-
retry_delay *= 2
|
|
96
|
+
retry_delay *= 2 # Exponential backoff
|
|
84
97
|
else:
|
|
85
|
-
self.console.error(f"Max retries ({
|
|
86
|
-
raise
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
self.console.error(f"Max retries ({num_retries}) exceeded. Last error: {e}")
|
|
99
|
+
raise e
|
|
100
|
+
|
|
101
|
+
def connection_check(self, client_version):
|
|
102
|
+
endpoint_url = f"{self.api_url}/connection_check"
|
|
103
|
+
headers = {"Content-Type": "application/json"}
|
|
104
|
+
payload = {
|
|
105
|
+
"api_key": self.api_key,
|
|
106
|
+
"client_version": client_version,
|
|
107
|
+
}
|
|
108
|
+
return self.post_request(endpoint_url, headers, payload, None, num_retries=0)
|
|
89
109
|
|
|
90
110
|
def render_functional_requirement(
|
|
91
111
|
self,
|
config/system_config.yaml
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
timeout:
|
|
3
|
-
command: timeout
|
|
4
|
-
error_message: |
|
|
5
|
-
Error: Required system command 'timeout' is not available.
|
|
6
|
-
This command is needed to enforce time limits on test execution.
|
|
7
|
-
|
|
8
|
-
To install the timeout command:
|
|
9
|
-
- On Linux: Install coreutils package
|
|
10
|
-
- Debian/Ubuntu: sudo apt-get install coreutils
|
|
11
|
-
- CentOS/RHEL: sudo yum install coreutils
|
|
12
|
-
- Fedora: sudo dnf install coreutils
|
|
13
|
-
- On macOS:
|
|
14
|
-
- Using Homebrew: brew install coreutils
|
|
15
|
-
- Using MacPorts: port install coreutils
|
|
16
|
-
- On Windows: Use Windows Subsystem for Linux (WSL), Cygwin, or Git Bash
|
|
1
|
+
client_version: "0.17.0"
|
|
17
2
|
|
|
18
3
|
error_messages:
|
|
19
4
|
template_not_found:
|
file_utils.py
CHANGED
|
@@ -43,6 +43,8 @@ FILE_EXTENSION_MAPPING = {
|
|
|
43
43
|
".bat": "Batch File",
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
SYSTEM_FOLDERS = [".git", CODEPLAIN_METADATA_FOLDER, CODEPLAIN_MEMORY_SUBFOLDER]
|
|
47
|
+
|
|
46
48
|
|
|
47
49
|
def get_file_type(file_name):
|
|
48
50
|
|
|
@@ -55,10 +57,9 @@ def get_file_type(file_name):
|
|
|
55
57
|
|
|
56
58
|
def list_all_text_files(directory):
|
|
57
59
|
all_files = []
|
|
58
|
-
skip_dirs = [".git", CODEPLAIN_METADATA_FOLDER, CODEPLAIN_MEMORY_SUBFOLDER]
|
|
59
60
|
for root, dirs, files in os.walk(directory, topdown=True):
|
|
60
61
|
# Skip directories that should not be traversed
|
|
61
|
-
for skip_dir in
|
|
62
|
+
for skip_dir in SYSTEM_FOLDERS:
|
|
62
63
|
if skip_dir in dirs:
|
|
63
64
|
dirs.remove(skip_dir)
|
|
64
65
|
|
|
@@ -322,5 +323,5 @@ def copy_folder_to_output(source_folder, output_folder):
|
|
|
322
323
|
if os.path.exists(output_folder):
|
|
323
324
|
delete_files_and_subfolders(output_folder)
|
|
324
325
|
|
|
325
|
-
# Copy source folder contents directly to output folder (excluding
|
|
326
|
-
copy_folder_content(source_folder, output_folder, ignore_folders=
|
|
326
|
+
# Copy source folder contents directly to output folder (excluding SYSTEM_FOLDERS)
|
|
327
|
+
copy_folder_content(source_folder, output_folder, ignore_folders=SYSTEM_FOLDERS)
|
git_utils.py
CHANGED
|
@@ -247,7 +247,10 @@ def diff(repo_path: Union[str, os.PathLike], previous_frid: str = None) -> dict:
|
|
|
247
247
|
|
|
248
248
|
def _get_commit(repo: Repo, frid: Optional[str]) -> str:
|
|
249
249
|
if frid:
|
|
250
|
-
|
|
250
|
+
commit_with_frid = _get_commit_with_frid(repo, frid)
|
|
251
|
+
if not commit_with_frid:
|
|
252
|
+
raise InvalidGitRepositoryError(f"No commit with frid {frid} found.")
|
|
253
|
+
return commit_with_frid
|
|
251
254
|
|
|
252
255
|
base_folder_commit = _get_base_folder_commit(repo)
|
|
253
256
|
initial_commit = _get_initial_commit(repo)
|
|
@@ -256,12 +259,44 @@ def _get_commit(repo: Repo, frid: Optional[str]) -> str:
|
|
|
256
259
|
return initial_commit
|
|
257
260
|
|
|
258
261
|
|
|
259
|
-
def _get_commit_with_frid(repo: Repo, frid: str) -> str:
|
|
260
|
-
"""
|
|
261
|
-
commit
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
def _get_commit_with_frid(repo: Repo, frid: str, module_name: Optional[str] = None) -> str:
|
|
263
|
+
"""
|
|
264
|
+
Finds commit with given frid mentioned in the commit message.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
repo (Repo): Git repository object
|
|
268
|
+
frid (str): Functional requirement ID
|
|
269
|
+
module_name (Optional[str]): Module name to filter by. If provided, only returns
|
|
270
|
+
commits that have both the FRID and module name.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
str: Commit SHA if found, empty string otherwise
|
|
274
|
+
"""
|
|
275
|
+
commit_message_pattern = FUNCTIONAL_REQUIREMENT_FINISHED_COMMIT_MESSAGE.format(frid)
|
|
276
|
+
|
|
277
|
+
# If no module name filtering is needed, use the original logic
|
|
278
|
+
if not module_name:
|
|
279
|
+
return _get_commit_with_message(repo, commit_message_pattern)
|
|
280
|
+
|
|
281
|
+
# Use multiple grep patterns with --all-match for AND condition
|
|
282
|
+
escaped_frid_message = commit_message_pattern.replace("[", "\\[").replace("]", "\\]")
|
|
283
|
+
module_name_pattern = MODULE_NAME_MESSAGE.format(module_name)
|
|
284
|
+
escaped_module_message = module_name_pattern.replace("[", "\\[").replace("]", "\\]")
|
|
285
|
+
|
|
286
|
+
return repo.git.rev_list(
|
|
287
|
+
repo.active_branch.name,
|
|
288
|
+
"--grep",
|
|
289
|
+
escaped_frid_message,
|
|
290
|
+
"--grep",
|
|
291
|
+
escaped_module_message,
|
|
292
|
+
"--all-match",
|
|
293
|
+
"-n",
|
|
294
|
+
"1",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def has_commit_for_frid(repo_path: Union[str, os.PathLike], frid: str, module_name: Optional[str] = None) -> bool:
|
|
299
|
+
return bool(_get_commit_with_frid(Repo(repo_path), frid, module_name))
|
|
265
300
|
|
|
266
301
|
|
|
267
302
|
def _get_base_folder_commit(repo: Repo) -> str:
|
memory_management.py
CHANGED
|
@@ -51,7 +51,7 @@ class MemoryManager:
|
|
|
51
51
|
code_diff_files = render_context.conformance_tests_running_context.code_diff_files
|
|
52
52
|
|
|
53
53
|
if not should_create_memory or code_diff_files is None:
|
|
54
|
-
console.
|
|
54
|
+
console.debug(
|
|
55
55
|
"Skipping creation of conformance test memory because the conditions for creating memories are not met."
|
|
56
56
|
)
|
|
57
57
|
return
|
module_renderer.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
+
import git_utils
|
|
4
5
|
import plain_file
|
|
5
6
|
import plain_modules
|
|
6
7
|
import plain_spec
|
|
@@ -8,6 +9,7 @@ from event_bus import EventBus
|
|
|
8
9
|
from memory_management import MemoryManager
|
|
9
10
|
from plain2code_console import console
|
|
10
11
|
from plain2code_events import RenderCompleted, RenderFailed
|
|
12
|
+
from plain2code_exceptions import MissingPreviousFunctionalitiesError
|
|
11
13
|
from plain2code_state import RunState
|
|
12
14
|
from plain_modules import PlainModule
|
|
13
15
|
from render_machine.code_renderer import CodeRenderer
|
|
@@ -35,6 +37,114 @@ class ModuleRenderer:
|
|
|
35
37
|
self.run_state = run_state
|
|
36
38
|
self.event_bus = event_bus
|
|
37
39
|
|
|
40
|
+
def _ensure_module_folders_exist(self, module_name: str, first_render_frid: str) -> tuple[str, str]:
|
|
41
|
+
"""
|
|
42
|
+
Ensure that build and conformance test folders exist for the module.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
module_name: Name of the module being rendered
|
|
46
|
+
first_render_frid: The first FRID in the render range
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
tuple[str, str]: (build_folder_path, conformance_tests_path)
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
MissingPreviousFridCommitsError: If any required folders are missing
|
|
53
|
+
"""
|
|
54
|
+
build_folder_path = os.path.join(self.args.build_folder, module_name)
|
|
55
|
+
conformance_tests_path = os.path.join(self.args.conformance_tests_folder, module_name)
|
|
56
|
+
|
|
57
|
+
if not os.path.exists(build_folder_path):
|
|
58
|
+
raise MissingPreviousFunctionalitiesError(
|
|
59
|
+
f"Cannot start rendering from functionality {first_render_frid} for module '{module_name}' because the source code folder does not exist.\n\n"
|
|
60
|
+
f"To fix this, please render the module from the beginning by running:\n"
|
|
61
|
+
f" codeplain {module_name}{plain_file.PLAIN_SOURCE_FILE_EXTENSION}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not os.path.exists(conformance_tests_path):
|
|
65
|
+
raise MissingPreviousFunctionalitiesError(
|
|
66
|
+
f"Cannot start rendering from functionality {first_render_frid} for module '{module_name}' because the conformance tests folder does not exist.\n\n"
|
|
67
|
+
f"To fix this, please render the module from the beginning by running:\n"
|
|
68
|
+
f" codeplain {module_name}{plain_file.PLAIN_SOURCE_FILE_EXTENSION}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return build_folder_path, conformance_tests_path
|
|
72
|
+
|
|
73
|
+
def _ensure_frid_commit_exists(
|
|
74
|
+
self,
|
|
75
|
+
frid: str,
|
|
76
|
+
module_name: str,
|
|
77
|
+
build_folder_path: str,
|
|
78
|
+
conformance_tests_path: str,
|
|
79
|
+
first_render_frid: str,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Ensure commit exists for a single FRID in both repositories.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
frid: The FRID to check
|
|
86
|
+
module_name: Name of the module
|
|
87
|
+
build_folder_path: Path to the build folder
|
|
88
|
+
conformance_tests_path: Path to the conformance tests folder
|
|
89
|
+
first_render_frid: The first FRID in the render range (for error messages)
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
MissingPreviousFridCommitsError: If the commit is missing
|
|
93
|
+
"""
|
|
94
|
+
# Check in build folder
|
|
95
|
+
if not git_utils.has_commit_for_frid(build_folder_path, frid, module_name):
|
|
96
|
+
raise MissingPreviousFunctionalitiesError(
|
|
97
|
+
f"Cannot start rendering from functionality {first_render_frid} for module '{module_name}' because the implementation of the previous functionality ({frid}) hasn't been completed yet.\n\n"
|
|
98
|
+
f"To fix this, please render the missing functionality ({frid}) first by running:\n"
|
|
99
|
+
f" codeplain {module_name}{plain_file.PLAIN_SOURCE_FILE_EXTENSION} --render-from {frid}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Check in conformance tests folder (only if conformance tests are enabled)
|
|
103
|
+
if self.args.render_conformance_tests:
|
|
104
|
+
if not git_utils.has_commit_for_frid(conformance_tests_path, frid, module_name):
|
|
105
|
+
raise MissingPreviousFunctionalitiesError(
|
|
106
|
+
f"Cannot start rendering from functionality {first_render_frid} for module '{module_name}' because the conformance tests for the previous functionality ({frid}) haven't been completed yet.\n\n"
|
|
107
|
+
f"To fix this, please render the missing functionality ({frid}) first by running:\n"
|
|
108
|
+
f" codeplain {module_name}{plain_file.PLAIN_SOURCE_FILE_EXTENSION} --render-from {frid}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _ensure_previous_frid_commits_exist(
|
|
112
|
+
self, module_name: str, plain_source: dict, render_range: list[str]
|
|
113
|
+
) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Ensure that all FRID commits before the render_range exist.
|
|
116
|
+
|
|
117
|
+
This is a precondition check that must pass before rendering can proceed.
|
|
118
|
+
Raises an exception if any previous FRID commits are missing.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
module_name: Name of the module being rendered
|
|
122
|
+
plain_source: The plain source tree
|
|
123
|
+
render_range: List of FRIDs to render
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
MissingPreviousFridCommitsError: If any previous FRID commits are missing
|
|
127
|
+
"""
|
|
128
|
+
first_render_frid = render_range[0]
|
|
129
|
+
|
|
130
|
+
# Get all FRIDs that should have been rendered before this one
|
|
131
|
+
previous_frids = plain_spec.get_frids_before(plain_source, first_render_frid)
|
|
132
|
+
if not previous_frids:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
# Ensure the module folders exist
|
|
136
|
+
build_folder_path, conformance_tests_path = self._ensure_module_folders_exist(module_name, first_render_frid)
|
|
137
|
+
|
|
138
|
+
# Verify commits exist for all previous FRIDs
|
|
139
|
+
for prev_frid in previous_frids:
|
|
140
|
+
self._ensure_frid_commit_exists(
|
|
141
|
+
prev_frid,
|
|
142
|
+
module_name,
|
|
143
|
+
build_folder_path,
|
|
144
|
+
conformance_tests_path,
|
|
145
|
+
first_render_frid,
|
|
146
|
+
)
|
|
147
|
+
|
|
38
148
|
def _build_render_context_for_module(
|
|
39
149
|
self,
|
|
40
150
|
module_name: str,
|
|
@@ -81,6 +191,10 @@ class ModuleRenderer:
|
|
|
81
191
|
resources_list = []
|
|
82
192
|
plain_spec.collect_linked_resources(plain_source, resources_list, None, True)
|
|
83
193
|
|
|
194
|
+
# Ensure that all previous FRID commits exist before proceeding with render_range
|
|
195
|
+
if render_range is not None:
|
|
196
|
+
self._ensure_previous_frid_commits_exist(module_name, plain_source, render_range)
|
|
197
|
+
|
|
84
198
|
required_modules = []
|
|
85
199
|
has_any_required_module_changed = False
|
|
86
200
|
if not self.args.render_machine_graph and required_modules_list:
|