codeplain 0.3.1__py3-none-any.whl → 0.3.2__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.
- _version.py +1 -1
- cli_output/render_summary.py +6 -1
- {codeplain-0.3.1.dist-info → codeplain-0.3.2.dist-info}/METADATA +20 -24
- {codeplain-0.3.1.dist-info → codeplain-0.3.2.dist-info}/RECORD +28 -30
- {codeplain-0.3.1.dist-info → codeplain-0.3.2.dist-info}/WHEEL +1 -1
- codeplain_REST_api.py +1 -1
- concept_utils.py +0 -1
- file_utils.py +18 -62
- plain2code.py +7 -3
- plain2code_exceptions.py +6 -0
- plain2code_state.py +4 -7
- plain2code_utils.py +0 -34
- plain_file.py +6 -0
- plain_modules.py +6 -2
- render_machine/actions/run_conformance_tests.py +4 -2
- render_machine/actions/run_unit_tests.py +2 -2
- render_machine/render_context.py +7 -2
- render_machine/render_types.py +0 -10
- system_config.py +0 -6
- tests/test_cli_output.py +9 -13
- tests/test_plainfileparser.py +4 -4
- tui/components.py +0 -6
- tui/plain2code_tui.py +4 -0
- tui/plain_module_render_choice_tui.py +9 -0
- tui/styles.css +1 -1
- tui/widget_helpers.py +0 -18
- hash_key.py +0 -29
- render_machine/conformance_test_helpers.py +0 -68
- {codeplain-0.3.1.dist-info → codeplain-0.3.2.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.1.dist-info → codeplain-0.3.2.dist-info}/licenses/LICENSE +0 -0
_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.2"
|
cli_output/render_summary.py
CHANGED
|
@@ -15,7 +15,12 @@ def print_exit_summary(
|
|
|
15
15
|
"""Print render outcome after the TUI exits (terminal restored)."""
|
|
16
16
|
console.quiet = False
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
if run_state.render_succeeded:
|
|
19
|
+
msg = "\n[#79FC96]✓ rendering completed\n\n"
|
|
20
|
+
elif run_state.render_cancelled:
|
|
21
|
+
msg = "\n[#FFFFFF]— rendering canceled\n\n"
|
|
22
|
+
else:
|
|
23
|
+
msg = "\n[#FF6B6B]✗ rendering failed\n\n"
|
|
19
24
|
msg += f" [#8E8F91]render id:\t\t\t[#FFFFFF]{run_state.render_id}\n"
|
|
20
25
|
msg += f" [#8E8F91]input file:\t\t\t[#FFFFFF]{spec_filename}\n"
|
|
21
26
|
msg += f" [#8E8F91]generated code folder:\t[#FFFFFF]{run_state.render_generated_code_path or '-'}\n\n"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Classifier: Environment :: Console
|
|
7
7
|
Classifier: Intended Audience :: Developers
|
|
8
8
|
Classifier: Operating System :: OS Independent
|
|
9
9
|
Classifier: Topic :: Software Development :: Code Generators
|
|
10
|
-
Requires-Python:
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
11
|
Requires-Dist: gitpython==3.1.50
|
|
12
12
|
Requires-Dist: mistletoe==1.3.0
|
|
13
13
|
Requires-Dist: networkx==3.6.1
|
|
@@ -29,31 +29,28 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
|
|
30
30
|
# Codeplain plain2code renderer
|
|
31
31
|
|
|
32
|
-
Render
|
|
32
|
+
Render \*\*\*plain source to software code using the Codeplain API.
|
|
33
33
|
|
|
34
34
|
## Codeplain.ai - Code Generation as a Service
|
|
35
35
|
|
|
36
|
-
Codeplain is a platform that generates software code using large language models based on requirements you specify in
|
|
36
|
+
Codeplain is a platform that generates software code using large language models based on requirements you specify in \*\*\*plain specification language.
|
|
37
37
|
|
|
38
38
|
Schematic overview of the Codeplain's code generation service
|
|
39
39
|
|
|
40
40
|
<img src="https://raw.githubusercontent.com/Codeplain-ai/plain2code_client/main/resources/codeplain_overview.png">
|
|
41
41
|
|
|
42
|
-
### Abstracting Away Code Generation Complexity with
|
|
42
|
+
### Abstracting Away Code Generation Complexity with \*\*\*plain
|
|
43
43
|
|
|
44
|
+
\*\*\*plain is a novel specification language that helps abstracting away complexity of using large language models for code generation.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
An example application in ***plain
|
|
46
|
+
An example application in \*\*\*plain
|
|
48
47
|
|
|
49
48
|
<img src="resources/plain_example.png" width="70%" height="70%">
|
|
50
49
|
|
|
51
|
-
|
|
52
50
|
## Getting started
|
|
53
51
|
|
|
54
52
|
### Prerequisites
|
|
55
53
|
|
|
56
|
-
|
|
57
54
|
#### System requirements
|
|
58
55
|
|
|
59
56
|
To run the plain2code client, you need Python 3.11 or a later version.
|
|
@@ -92,18 +89,19 @@ export CODEPLAIN_API_KEY="your_actual_api_key_here"
|
|
|
92
89
|
After completing the installation steps above, you can immediately test the system with a simple "Hello World" example:
|
|
93
90
|
|
|
94
91
|
- Change to the example folder and run the example:
|
|
95
|
-
```
|
|
96
|
-
cd examples/example_hello_world_python
|
|
97
|
-
python ../../plain2code.py hello_world_python.plain
|
|
98
|
-
```
|
|
99
92
|
|
|
100
|
-
|
|
93
|
+
```
|
|
94
|
+
cd examples/example_hello_world_python
|
|
95
|
+
python ../../plain2code.py hello_world_python.plain
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
_Note: Rendering will take a few minutes to complete._
|
|
101
99
|
|
|
102
100
|
- The system will generate a Python application in the `build` directory. You can run it with:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
```
|
|
102
|
+
cd build
|
|
103
|
+
python hello_world.py
|
|
104
|
+
```
|
|
107
105
|
|
|
108
106
|
## Releasing
|
|
109
107
|
|
|
@@ -112,7 +110,7 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
|
|
|
112
110
|
1. Bump the version in `_version.py` and commit/tag (`git tag v<version>`).
|
|
113
111
|
2. Build the distributions:
|
|
114
112
|
```bash
|
|
115
|
-
uv build
|
|
113
|
+
uv build --python 3.11
|
|
116
114
|
```
|
|
117
115
|
This produces `dist/codeplain-<version>-py3-none-any.whl` and `dist/codeplain-<version>.tar.gz`.
|
|
118
116
|
3. (Optional) Smoke-test the wheel:
|
|
@@ -135,13 +133,11 @@ Releases are built and published with [uv](https://docs.astral.sh/uv/). The vers
|
|
|
135
133
|
### Examples and Sample Projects
|
|
136
134
|
|
|
137
135
|
- See the [examples](examples) folder for sample projects in Golang, Python, and React.
|
|
138
|
-
- For example application how to implement task manager in
|
|
139
|
-
- For example application how to implement SaaS connectors in
|
|
136
|
+
- For example application how to implement task manager in \*\*\*plain see [example-task-manager](https://github.com/Codeplain-ai/example-task-manager) repository.
|
|
137
|
+
- For example application how to implement SaaS connectors in \*\*\*plain see [example-saas-connectors](https://github.com/Codeplain-ai/example-saas-connectors) repository.
|
|
140
138
|
|
|
141
139
|
### Documentation
|
|
142
140
|
|
|
143
141
|
- For more details on the ***plain format, see the [***plain language specification](https://www.plainlang.org/docs/).
|
|
144
142
|
- For step-by-step instructions for creating your first ***plain project see the [Kickstart your ***plain project](docs/starting_a_plain_project_from_scratch.md).
|
|
145
143
|
- For complete CLI documentation and usage examples, see [plain2code CLI documentation](docs/plain2code_cli.md).
|
|
146
|
-
|
|
147
|
-
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
_version.py,sha256=
|
|
2
|
-
codeplain_REST_api.py,sha256=
|
|
3
|
-
concept_utils.py,sha256=
|
|
1
|
+
_version.py,sha256=vNiWJ14r_cw5t_7UDqDQIVZvladKFGyHH2avsLpN7Vg,22
|
|
2
|
+
codeplain_REST_api.py,sha256=h_XoNws8fvf7OTtoVXhHh6OfLGg77UNFuggMBwaqBwE,21533
|
|
3
|
+
concept_utils.py,sha256=D4qVHFBRvfJZwiIq1AD_SV0Ev-RnOMg2yGWo6Cal7PM,7913
|
|
4
4
|
diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
|
|
5
5
|
event_bus.py,sha256=g5XLwRfjrvTIcB6urjREuCJzZj5TA93hy8lKXLGXuCE,650
|
|
6
|
-
file_utils.py,sha256=
|
|
6
|
+
file_utils.py,sha256=grPx2Rl6-q2ijSRoaAOZcsrpOjTqb4i_mtF-jzwhKcQ,10953
|
|
7
7
|
git_utils.py,sha256=LQQnL5DAFpH5fDBQgUZX2WfjWY4YC5OEGBPIXryTmnY,16896
|
|
8
|
-
hash_key.py,sha256=lMKgKpOhPeC4UpFf9e7eCNK20FEaDsXTCJ8j3eCWidE,837
|
|
9
8
|
memory_management.py,sha256=nHNudgaOqp5UGkIMi62TRCc2nUeFOM0w-I1FfxBFaQE,6120
|
|
10
9
|
module_renderer.py,sha256=pXMXKKGMhUwLLlSxppE_hpZlwpJ3WSHSfR9FQxt6rSI,6860
|
|
11
10
|
partial_rendering.py,sha256=prWEJo9m1TbF7SKzpNInbcefyTF0o3oXE7Qf1l4SmBs,8409
|
|
12
|
-
plain2code.py,sha256=
|
|
11
|
+
plain2code.py,sha256=gyPeZtLe7VkGChWwGVNNZRXvkqpH8uC27szHbrZgmeY,14010
|
|
13
12
|
plain2code_arguments.py,sha256=J05NwaY-O04rIWVF2yIXysNB_ofZS0wJFn0u1WaCCSA,15399
|
|
14
13
|
plain2code_console.py,sha256=YdE-Kz9qRAzIIAg7aYA1bnXRcwy_aTpbFtkR2zHbd-I,4918
|
|
15
14
|
plain2code_events.py,sha256=AM8KJS68uiVXRMLt861KaGhs2H4yzujlMB_xiYPfHQc,1478
|
|
16
|
-
plain2code_exceptions.py,sha256=
|
|
15
|
+
plain2code_exceptions.py,sha256=Fj1pwCffn06aivEplsTJJGdg6lAOR8gmWd4ctLl9cj4,1745
|
|
17
16
|
plain2code_logger.py,sha256=kNKIfkS6Ydtut7Ptw5jmv5bFPgXlxB-_Ds5nd-j4d9o,4609
|
|
18
17
|
plain2code_nodes.py,sha256=IwjQqJxRSJKgP6eEnkYGy3VQHIyoazgkFxAEkxkjfJ8,4426
|
|
19
18
|
plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,2235
|
|
20
|
-
plain2code_state.py,sha256=
|
|
21
|
-
plain2code_utils.py,sha256=
|
|
22
|
-
plain_file.py,sha256=
|
|
23
|
-
plain_modules.py,sha256=
|
|
19
|
+
plain2code_state.py,sha256=gmqbGfM2dx6ieiqRQRlLPD49JsMAyb3lOWspljcTj9g,1944
|
|
20
|
+
plain2code_utils.py,sha256=HYDJ4lTC3RwBgcJadPvcoU3-Y6krNiuDP4_t2xYKA2Q,874
|
|
21
|
+
plain_file.py,sha256=vJKCkIA12o7LHAGugIsTbzcpoQrTQX2uqlXEC_EqfPo,29470
|
|
22
|
+
plain_modules.py,sha256=NXUtxW8fEpJAuEIeVE_iwRBCbHFh2ji-p9xdkgojLqU,14423
|
|
24
23
|
plain_spec.py,sha256=E2Yp2eXMExE7kx7GPQ7v9boHN8XhFUcqprn1DGMaFMs,15669
|
|
25
|
-
system_config.py,sha256=
|
|
24
|
+
system_config.py,sha256=trgytfXKXoCn0Pgy9u00x2D0ZRHlh_tADlbkwZnF8QE,1038
|
|
26
25
|
cli_output/__init__.py,sha256=MBKl0YbVpWE-tBa3V5_aVbB7vbaqlIK2NsHTjRUZqis,284
|
|
27
26
|
cli_output/dry_run.py,sha256=6NKhNWPtD0CBK_QHSsSqIKLTzibTwxS9d8ltP9d1bgU,1473
|
|
28
|
-
cli_output/render_summary.py,sha256=
|
|
27
|
+
cli_output/render_summary.py,sha256=QrW3Iq15xUoWXuVXpqClAKncAvHdQR0lWG1_OMUBsWk,1307
|
|
29
28
|
cli_output/status.py,sha256=5jr9XFU1MWD3pN404qw7SNYCGdvqfA9j4xv9z1tZG4s,5890
|
|
30
29
|
config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
|
|
31
30
|
config/system_config.yaml,sha256=of5R9vukSohU9AlkGJOcAVQA1UJgxYmDy6islU6iFuE,508
|
|
@@ -33,11 +32,10 @@ docs/generate_cli.py,sha256=0FHVhICbM8g7ahdInTNih3hNKpty1wbV5rdGECMJj48,656
|
|
|
33
32
|
examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py,sha256=dwTowrHiVKKbrDv21v8xJC30Q57AXZkQasdGOO5JsBE,470
|
|
34
33
|
render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
34
|
render_machine/code_renderer.py,sha256=bLdoZcWaLGvuR0GygnWY9y9kycGySD-IEWY5DFGcGsg,3781
|
|
36
|
-
render_machine/conformance_test_helpers.py,sha256=6Ru7Dh24SLNIKWfz_sP5hE_EmWpvk6nfgN1T1yaCZus,2887
|
|
37
35
|
render_machine/conformance_tests.py,sha256=MeEveEGJTClLGxWw04P4TR6U9kX9Qgz9qtxNrB9a8QI,7085
|
|
38
36
|
render_machine/implementation_code_helpers.py,sha256=LdHNLoKgVtrDaGH2APX8C9vjNjLBUhu2V7OAaJvm4rM,2009
|
|
39
|
-
render_machine/render_context.py,sha256=
|
|
40
|
-
render_machine/render_types.py,sha256=
|
|
37
|
+
render_machine/render_context.py,sha256=NHDtJkhQP8tW0iyRH6PR7oSk-PO_pEGMKkX38x52YMc,29128
|
|
38
|
+
render_machine/render_types.py,sha256=cT3D0lmxQ3enIo0jGp3LEGCY2t3X1Q1_tqwcadzSAv0,8761
|
|
41
39
|
render_machine/render_utils.py,sha256=ao0e64VSOGYfb8hwJXIZlbqer5HjNMYJWHlLDoHnJyc,9123
|
|
42
40
|
render_machine/state_machine_config.py,sha256=uqrU9Pu4cAKxu_-e1eiuUQK-tZWxZko0U0Bfvl67v_c,28813
|
|
43
41
|
render_machine/states.py,sha256=niFXqayyA_rezCrlzo0hYeHAruErcPzQVF-BeIBGnL8,1968
|
|
@@ -56,8 +54,8 @@ render_machine/actions/prepare_testing_environment.py,sha256=GQNVzc_Ap5n74OAmeQu
|
|
|
56
54
|
render_machine/actions/refactor_code.py,sha256=_1C16bQbpxdzJkEwl8jDhEKVyXcyGTd-5YYDNTq2vA8,1782
|
|
57
55
|
render_machine/actions/render_conformance_tests.py,sha256=5gOQn-G1T9UWEI8sDfALcVdArk0ln9DgUprOPjccS-U,9268
|
|
58
56
|
render_machine/actions/render_functional_requirement.py,sha256=2SbPGsLbrrQx3kasXpSpphzaMsq9GD8ASnSzvIdodT4,3748
|
|
59
|
-
render_machine/actions/run_conformance_tests.py,sha256=
|
|
60
|
-
render_machine/actions/run_unit_tests.py,sha256
|
|
57
|
+
render_machine/actions/run_conformance_tests.py,sha256=4sJTORl6YLFXQIdLMBQHDeJd2hztGyt3tsVvrCviBko,4008
|
|
58
|
+
render_machine/actions/run_unit_tests.py,sha256=A4WXtSzpCLDHYR8fQAbfxUf_E19GYflZSHZaSreoFPk,2101
|
|
61
59
|
render_machine/actions/summarize_conformance_tests.py,sha256=Vy1kRK8vyazK_BbsKuz1MLDN_TQWVxxzkwEEm-50byc,1639
|
|
62
60
|
standard_template_library/__init__.py,sha256=LtsHl0c3VV46DUInIqyOw4KKVoxKEMOx8dAZpMXtAXc,43
|
|
63
61
|
standard_template_library/golang-console-app-template.plain,sha256=JOusEjCj7jagnXUzaI-SdIr3hj1wTo2uShK017aNtVM,949
|
|
@@ -66,14 +64,14 @@ standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOz
|
|
|
66
64
|
standard_template_library/typescript-react-app-template.plain,sha256=DN5wEJcT8ZqQTf0vFKi9Z1isi9cin2UVNOZb3r28d3M,715
|
|
67
65
|
tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
68
66
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
69
|
-
tests/test_cli_output.py,sha256=
|
|
67
|
+
tests/test_cli_output.py,sha256=YdIQtplncTeMf82E0nWNrGYoYAFI8Man4zneYkWmbxk,14425
|
|
70
68
|
tests/test_file_utils.py,sha256=-O5zImiFVz9JJ4xi8TbsUwNgPFQoQUOl9Frls4BHCBI,1358
|
|
71
69
|
tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
|
|
72
70
|
tests/test_imports.py,sha256=EWYLUxGunUUdsG9iCiY_tts6T7DIVdhdB_MZlB1LsmU,2081
|
|
73
71
|
tests/test_partial_rendering.py,sha256=4xXZhy9yStn0awSw8B-yrJ3kVYyzJrlR-dKwg9ODXMY,11188
|
|
74
72
|
tests/test_plain_modules.py,sha256=9bWsketDnjpCWkCbMZzYvLqT1ojPhLyxxKljPUq42oY,11393
|
|
75
73
|
tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
|
|
76
|
-
tests/test_plainfileparser.py,sha256=
|
|
74
|
+
tests/test_plainfileparser.py,sha256=7AassxH7AKX4lfW6E_58FYxRe-IzIiA16BMVWPjWqCM,18750
|
|
77
75
|
tests/test_plainspec.py,sha256=JrzjJ4VFYDmWTZDq-8WFyt2oMx76IEuO1CAUPQeGOsw,2404
|
|
78
76
|
tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
|
|
79
77
|
tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
|
|
@@ -81,16 +79,16 @@ tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
|
|
|
81
79
|
tests/e2e/test_hello_world_python.py,sha256=PQy4elzB7WvpiWLKa6_HFO0K8HA6_6hGLaAqRi0J3OU,1627
|
|
82
80
|
tests/e2e/test_hello_world_python_windows.py,sha256=kJQZIuzj8OEEtgBVxnzFvyQJ_wvP2oBu9L-E9geg8sE,2010
|
|
83
81
|
tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
|
|
84
|
-
tui/components.py,sha256=
|
|
82
|
+
tui/components.py,sha256=DoMDhwNVVpv--OY6o8YQYPzOQPG9Dyh9kgMOqUYtNiE,25412
|
|
85
83
|
tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
|
|
86
|
-
tui/plain2code_tui.py,sha256=
|
|
87
|
-
tui/plain_module_render_choice_tui.py,sha256=
|
|
84
|
+
tui/plain2code_tui.py,sha256=t-0DonjrK6_x6jPUMYW0w6tvjZZlk89E-DEor7GzjG4,13785
|
|
85
|
+
tui/plain_module_render_choice_tui.py,sha256=MLc-Ps3O2DMBKRCPJAxUY059SdDIYq2ha7-sWxuguj0,9611
|
|
88
86
|
tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
89
87
|
tui/state_handlers.py,sha256=4r_6m5YMcprQtgiTP0De3XFfGAF8VzgaWXHxPweYPbQ,16325
|
|
90
|
-
tui/styles.css,sha256=
|
|
91
|
-
tui/widget_helpers.py,sha256=
|
|
92
|
-
codeplain-0.3.
|
|
93
|
-
codeplain-0.3.
|
|
94
|
-
codeplain-0.3.
|
|
95
|
-
codeplain-0.3.
|
|
96
|
-
codeplain-0.3.
|
|
88
|
+
tui/styles.css,sha256=c13WY1nOgmtkT_FH9Ad2PjXrfi8LYwr6zzD6rBOh9tU,6685
|
|
89
|
+
tui/widget_helpers.py,sha256=mWDPrALsxLOzJg8KD9MXXafJKRiqG-EIp797jbOT77o,5972
|
|
90
|
+
codeplain-0.3.2.dist-info/METADATA,sha256=_m-JTZCg09Ur5aGhureD24GwhQpQravmQnsFxfT37bc,5134
|
|
91
|
+
codeplain-0.3.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
92
|
+
codeplain-0.3.2.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
93
|
+
codeplain-0.3.2.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
|
|
94
|
+
codeplain-0.3.2.dist-info/RECORD,,
|
codeplain_REST_api.py
CHANGED
|
@@ -108,7 +108,7 @@ class CodeplainAPI:
|
|
|
108
108
|
if error_code == "InternalServerError":
|
|
109
109
|
raise plain2code_exceptions.InternalServerError(
|
|
110
110
|
"Internal server error.\n\n"
|
|
111
|
-
|
|
111
|
+
"Please report the error to support@codeplain.ai with the attached .log file."
|
|
112
112
|
)
|
|
113
113
|
exception_class = ERROR_CODE_EXCEPTIONS[error_code]
|
|
114
114
|
raise exception_class(message)
|
concept_utils.py
CHANGED
file_utils.py
CHANGED
|
@@ -54,15 +54,6 @@ def is_system_folder_path(file_path: str) -> bool:
|
|
|
54
54
|
return bool(parts) and parts[0] in SYSTEM_FOLDERS
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def get_file_type(file_name):
|
|
58
|
-
|
|
59
|
-
# Extract the file extension
|
|
60
|
-
ext = Path(file_name).suffix.lower() # Convert to lowercase to handle case-insensitive matching
|
|
61
|
-
|
|
62
|
-
# Use the dictionary to get the file type, defaulting to 'unknown' if the extension is not found
|
|
63
|
-
return FILE_EXTENSION_MAPPING.get(ext, "unknown")
|
|
64
|
-
|
|
65
|
-
|
|
66
57
|
def list_all_text_files(directory):
|
|
67
58
|
all_files = []
|
|
68
59
|
for root, dirs, files in os.walk(directory, topdown=True):
|
|
@@ -99,64 +90,29 @@ def list_folders_in_directory(directory):
|
|
|
99
90
|
return folders
|
|
100
91
|
|
|
101
92
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
delete_files_and_subfolders(folder_name)
|
|
107
|
-
_make_writable(folder_name)
|
|
108
|
-
os.rmdir(folder_name)
|
|
93
|
+
def _on_rm_error(func, path, _exc_info):
|
|
94
|
+
"""On Windows, clear read-only flag and retry the removal."""
|
|
95
|
+
os.chmod(path, stat.S_IWRITE)
|
|
96
|
+
func(path)
|
|
109
97
|
|
|
110
98
|
|
|
111
|
-
def
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if is_windows:
|
|
116
|
-
try:
|
|
117
|
-
mode = os.stat(path).st_mode
|
|
118
|
-
os.chmod(path, mode | stat.S_IWRITE)
|
|
119
|
-
except OSError:
|
|
120
|
-
pass
|
|
99
|
+
def delete_folder(folder_name):
|
|
100
|
+
"""Delete a folder and all its subfolders and files."""
|
|
101
|
+
if os.path.exists(folder_name):
|
|
102
|
+
shutil.rmtree(folder_name, onerror=_on_rm_error)
|
|
121
103
|
|
|
122
104
|
|
|
123
105
|
def delete_files_and_subfolders(directory):
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
total_files_deleted += 1
|
|
135
|
-
|
|
136
|
-
# Delete directories
|
|
137
|
-
for dir_ in dirs:
|
|
138
|
-
dir_path = os.path.join(root, dir_)
|
|
139
|
-
_make_writable(dir_path)
|
|
140
|
-
os.rmdir(dir_path)
|
|
141
|
-
total_folders_deleted += 1
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def copy_file(source_path, destination_path):
|
|
145
|
-
# Ensure the destination directory exists
|
|
146
|
-
os.makedirs(os.path.dirname(destination_path), exist_ok=True)
|
|
147
|
-
|
|
148
|
-
# Open the source file in read-binary ('rb') mode
|
|
149
|
-
with open(source_path, "rb") as source_file:
|
|
150
|
-
# Open the destination file in write-binary ('wb') mode
|
|
151
|
-
with open(destination_path, "wb") as destination_file:
|
|
152
|
-
# Copy the content from source to destination
|
|
153
|
-
while True:
|
|
154
|
-
# Read a chunk of the source file
|
|
155
|
-
chunk = source_file.read(4096) # Reading in chunks of 4KB
|
|
156
|
-
if not chunk:
|
|
157
|
-
break # End of file reached
|
|
158
|
-
# Write the chunk to the destination file
|
|
159
|
-
destination_file.write(chunk)
|
|
106
|
+
"""Delete all contents of a directory but keep the directory itself."""
|
|
107
|
+
for entry in os.scandir(directory):
|
|
108
|
+
if entry.is_dir(follow_symlinks=False):
|
|
109
|
+
shutil.rmtree(entry.path, onerror=_on_rm_error)
|
|
110
|
+
else:
|
|
111
|
+
try:
|
|
112
|
+
os.remove(entry.path)
|
|
113
|
+
except PermissionError:
|
|
114
|
+
os.chmod(entry.path, stat.S_IWRITE)
|
|
115
|
+
os.remove(entry.path)
|
|
160
116
|
|
|
161
117
|
|
|
162
118
|
def add_current_path_if_no_path(filename):
|
plain2code.py
CHANGED
|
@@ -25,6 +25,7 @@ from plain2code_console import console
|
|
|
25
25
|
from plain2code_events import RenderFailed
|
|
26
26
|
from plain2code_exceptions import (
|
|
27
27
|
ConflictingRequirements,
|
|
28
|
+
GitNotInstalledError,
|
|
28
29
|
InvalidAPIKey,
|
|
29
30
|
InvalidFridArgument,
|
|
30
31
|
MissingAPIKey,
|
|
@@ -181,6 +182,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
|
|
|
181
182
|
render_choices,
|
|
182
183
|
system_config.client_version,
|
|
183
184
|
run_state.render_id,
|
|
185
|
+
on_cancel=run_state.set_render_cancelled,
|
|
184
186
|
css_path="styles.css",
|
|
185
187
|
)
|
|
186
188
|
render_choice = app.run()
|
|
@@ -189,6 +191,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
|
|
|
189
191
|
and render_choice.render_range is None
|
|
190
192
|
and render_choice.choice_type == "quit"
|
|
191
193
|
):
|
|
194
|
+
run_state.set_render_cancelled()
|
|
192
195
|
sys.exit(0)
|
|
193
196
|
elif ask_user and args.headless:
|
|
194
197
|
# ignore the default choice if it requires user input due to headless mode
|
|
@@ -217,7 +220,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
|
|
|
217
220
|
try:
|
|
218
221
|
module_renderer.render_module()
|
|
219
222
|
except RenderCancelledError:
|
|
220
|
-
|
|
223
|
+
run_state.set_render_cancelled() # TUI already closed, nothing to report
|
|
221
224
|
except Exception as e:
|
|
222
225
|
run_state.set_render_succeeded(False)
|
|
223
226
|
render_error.append(e)
|
|
@@ -228,8 +231,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
|
|
|
228
231
|
try:
|
|
229
232
|
module_renderer.render_module()
|
|
230
233
|
except RenderCancelledError:
|
|
231
|
-
run_state.
|
|
232
|
-
pass
|
|
234
|
+
run_state.set_render_cancelled()
|
|
233
235
|
return
|
|
234
236
|
else:
|
|
235
237
|
render_thread = threading.Thread(target=run_render, daemon=True)
|
|
@@ -242,6 +244,7 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
|
|
|
242
244
|
prepare_environment_script=args.prepare_environment_script,
|
|
243
245
|
state_machine_version=system_config.client_version,
|
|
244
246
|
enter_pause_event=enter_pause_event,
|
|
247
|
+
on_cancel=run_state.set_render_cancelled,
|
|
245
248
|
default_log_level=default_log_level,
|
|
246
249
|
css_path="styles.css",
|
|
247
250
|
)
|
|
@@ -354,6 +357,7 @@ def main(): # noqa: C901
|
|
|
354
357
|
NetworkConnectionError,
|
|
355
358
|
ModuleDoesNotExistError,
|
|
356
359
|
UnsupportedResourceType,
|
|
360
|
+
GitNotInstalledError,
|
|
357
361
|
),
|
|
358
362
|
):
|
|
359
363
|
exc_info = sys.exc_info()
|
plain2code_exceptions.py
CHANGED
plain2code_state.py
CHANGED
|
@@ -11,6 +11,7 @@ class RunState:
|
|
|
11
11
|
def __init__(self, spec_filename: str, replay_with: Optional[str] = None):
|
|
12
12
|
self.replay: bool = replay_with is not None
|
|
13
13
|
self.render_succeeded: bool = False
|
|
14
|
+
self.render_cancelled: bool = False
|
|
14
15
|
self.render_generated_code_path: Optional[str] = None
|
|
15
16
|
self.rendered_functionalities: int = 0
|
|
16
17
|
if replay_with:
|
|
@@ -20,7 +21,6 @@ class RunState:
|
|
|
20
21
|
self.spec_filename: str = spec_filename
|
|
21
22
|
self.call_count: int = 0
|
|
22
23
|
self.unittest_batch_id: int = 0
|
|
23
|
-
self.frid_render_anaysis: dict[str, str] = {}
|
|
24
24
|
self.render_time_accumulated: int = 0
|
|
25
25
|
self.last_render_start_timestamp: float = time.monotonic()
|
|
26
26
|
|
|
@@ -30,12 +30,12 @@ class RunState:
|
|
|
30
30
|
def increment_unittest_batch_id(self):
|
|
31
31
|
self.unittest_batch_id += 1
|
|
32
32
|
|
|
33
|
-
def add_rendering_analysis_for_frid(self, frid, rendering_analysis) -> None:
|
|
34
|
-
self.frid_render_anaysis[frid] = rendering_analysis
|
|
35
|
-
|
|
36
33
|
def set_render_succeeded(self, succeeded: bool):
|
|
37
34
|
self.render_succeeded = succeeded
|
|
38
35
|
|
|
36
|
+
def set_render_cancelled(self):
|
|
37
|
+
self.render_cancelled = True
|
|
38
|
+
|
|
39
39
|
def set_render_generated_code_path(self, generated_code_path: str):
|
|
40
40
|
self.render_generated_code_path = generated_code_path
|
|
41
41
|
|
|
@@ -55,6 +55,3 @@ class RunState:
|
|
|
55
55
|
"replay": self.replay,
|
|
56
56
|
"spec_filename": self.spec_filename,
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
def get_render_func_id(self, frid: str) -> str:
|
|
60
|
-
return f"{self.render_id}-{frid}"
|
plain2code_utils.py
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
import plain_spec
|
|
4
|
-
from plain2code_console import console
|
|
5
|
-
|
|
6
|
-
|
|
7
1
|
def format_duration_hms(total_seconds: int) -> str:
|
|
8
2
|
"""Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``)."""
|
|
9
3
|
if total_seconds < 0:
|
|
@@ -26,31 +20,3 @@ AMBIGUITY_CAUSES = {
|
|
|
26
20
|
"functional_requirement_ambiguity": "Ambiguity is in the functionality",
|
|
27
21
|
"other": "Ambiguity in the other parts of the specification",
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def print_dry_run_output(plain_source_tree: dict, render_range: Optional[list[str]]):
|
|
32
|
-
frid = plain_spec.get_first_frid(plain_source_tree)
|
|
33
|
-
|
|
34
|
-
while frid is not None:
|
|
35
|
-
is_inside_range = render_range is None or frid in render_range
|
|
36
|
-
|
|
37
|
-
if is_inside_range:
|
|
38
|
-
specifications, _ = plain_spec.get_specifications_for_frid(plain_source_tree, frid)
|
|
39
|
-
functional_requirement_text = specifications[plain_spec.FUNCTIONAL_REQUIREMENTS][-1]
|
|
40
|
-
console.info(
|
|
41
|
-
"-------------------------------------\n"
|
|
42
|
-
f"Rendering functionality {frid}:\n"
|
|
43
|
-
f"{functional_requirement_text}\n"
|
|
44
|
-
"-------------------------------------\n"
|
|
45
|
-
)
|
|
46
|
-
if plain_spec.ACCEPTANCE_TESTS in specifications:
|
|
47
|
-
for i, acceptance_test in enumerate(specifications[plain_spec.ACCEPTANCE_TESTS], 1):
|
|
48
|
-
console.info(f"Generating acceptance test #{i}:\n\n{acceptance_test}\n")
|
|
49
|
-
else:
|
|
50
|
-
console.info(
|
|
51
|
-
"-------------------------------------\n"
|
|
52
|
-
f"Skipping rendering iteration: {frid}\n"
|
|
53
|
-
"-------------------------------------"
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
frid = plain_spec.get_next_frid(plain_source_tree, frid)
|
plain_file.py
CHANGED
|
@@ -91,6 +91,12 @@ def check_section_for_linked_resources(section):
|
|
|
91
91
|
f"Plain syntax error: Only relative links are allowed (text: {link.node.children[0].content}, target: {link.node.target})."
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
+
if not os.path.exists(link.node.target):
|
|
95
|
+
raise PlainSyntaxError(f"Plain syntax error: Link {link.node.target} does not exist.")
|
|
96
|
+
|
|
97
|
+
if not os.path.isfile(link.node.target):
|
|
98
|
+
raise PlainSyntaxError(f"Plain syntax error: Link {link.node.target} must be a file.")
|
|
99
|
+
|
|
94
100
|
if len(link.node.children) != 1:
|
|
95
101
|
raise PlainSyntaxError(f"Plain syntax error: Link must have text specified (link: {link.node.target}).")
|
|
96
102
|
|
plain_modules.py
CHANGED
|
@@ -5,13 +5,17 @@ import os
|
|
|
5
5
|
import shutil
|
|
6
6
|
from functools import cached_property
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from plain2code_exceptions import GitNotInstalledError, MissingPreviousFunctionalitiesError, ModuleDoesNotExistError
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from git.exc import NoSuchPathError
|
|
12
|
+
except ImportError:
|
|
13
|
+
raise GitNotInstalledError("git is not installed. Please install git and try again.")
|
|
9
14
|
|
|
10
15
|
import git_utils
|
|
11
16
|
import plain_file
|
|
12
17
|
import plain_spec
|
|
13
18
|
from plain2code_console import console
|
|
14
|
-
from plain2code_exceptions import MissingPreviousFunctionalitiesError, ModuleDoesNotExistError
|
|
15
19
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
16
20
|
|
|
17
21
|
CODEPLAIN_MEMORY_SUBFOLDER = ".memory"
|
|
@@ -40,7 +40,7 @@ class RunConformanceTests(BaseAction):
|
|
|
40
40
|
+ f"in module {render_context.conformance_tests_running_context.current_testing_module_name}"
|
|
41
41
|
+ ")."
|
|
42
42
|
)
|
|
43
|
-
exit_code, conformance_tests_issue,
|
|
43
|
+
exit_code, conformance_tests_issue, conformance_tests_temp_log_file_path = render_utils.execute_script(
|
|
44
44
|
conformance_tests_script,
|
|
45
45
|
[render_context.build_folder, conformance_tests_folder_name],
|
|
46
46
|
"Conformance Tests",
|
|
@@ -49,7 +49,9 @@ class RunConformanceTests(BaseAction):
|
|
|
49
49
|
timeout=render_context.test_script_timeout,
|
|
50
50
|
stop_event=render_context.stop_event,
|
|
51
51
|
)
|
|
52
|
-
render_context.script_execution_history.latest_conformance_test_output_path =
|
|
52
|
+
render_context.script_execution_history.latest_conformance_test_output_path = (
|
|
53
|
+
conformance_tests_temp_log_file_path
|
|
54
|
+
)
|
|
53
55
|
render_context.script_execution_history.should_update_script_outputs = True
|
|
54
56
|
|
|
55
57
|
render_context.memory_manager.create_conformance_tests_memory(
|
|
@@ -21,7 +21,7 @@ class RunUnitTests(BaseAction):
|
|
|
21
21
|
console.info(
|
|
22
22
|
f"Running unit tests script {unittests_script}. (attempt: {render_context.unit_tests_running_context.fix_attempts + 1})"
|
|
23
23
|
)
|
|
24
|
-
exit_code, unittests_issue,
|
|
24
|
+
exit_code, unittests_issue, unittests_temp_log_file_path = render_utils.execute_script(
|
|
25
25
|
unittests_script,
|
|
26
26
|
[render_context.build_folder],
|
|
27
27
|
"Unit Tests",
|
|
@@ -29,7 +29,7 @@ class RunUnitTests(BaseAction):
|
|
|
29
29
|
stop_event=render_context.stop_event,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
render_context.script_execution_history.latest_unit_test_output_path =
|
|
32
|
+
render_context.script_execution_history.latest_unit_test_output_path = unittests_temp_log_file_path
|
|
33
33
|
render_context.script_execution_history.should_update_script_outputs = True
|
|
34
34
|
if exit_code == 0:
|
|
35
35
|
return self.SUCCESSFUL_OUTCOME, None
|
render_machine/render_context.py
CHANGED
|
@@ -179,7 +179,11 @@ class RenderContext:
|
|
|
179
179
|
)
|
|
180
180
|
|
|
181
181
|
def has_next_frid(self) -> bool:
|
|
182
|
-
|
|
182
|
+
next_frid = plain_spec.get_next_frid(self.plain_source_tree, self.frid_context.frid)
|
|
183
|
+
if self.render_range is None or len(self.render_range) == 0:
|
|
184
|
+
return next_frid is not None
|
|
185
|
+
|
|
186
|
+
return next_frid is not None and int(next_frid) <= int(self.render_range[-1])
|
|
183
187
|
|
|
184
188
|
def finish_implementing_frid(self):
|
|
185
189
|
self.functional_requirements_render_attempts_failed_unit_during_conformance_tests = 0
|
|
@@ -248,7 +252,8 @@ class RenderContext:
|
|
|
248
252
|
conformance_tests_running_context = self.conformance_tests_running_context
|
|
249
253
|
if conformance_tests_running_context.current_testing_module_name == self.module_name:
|
|
250
254
|
conformance_tests_running_context.current_testing_frid = plain_spec.get_next_frid(
|
|
251
|
-
self.plain_source_tree,
|
|
255
|
+
self.plain_source_tree,
|
|
256
|
+
self.conformance_tests_running_context.current_testing_frid,
|
|
252
257
|
)
|
|
253
258
|
else:
|
|
254
259
|
all_frids = list(
|
render_machine/render_types.py
CHANGED
|
@@ -139,16 +139,6 @@ class ConformanceTestsRunningContext:
|
|
|
139
139
|
"test_summary"
|
|
140
140
|
] = summary
|
|
141
141
|
|
|
142
|
-
def get_context_summary(self) -> dict:
|
|
143
|
-
return {
|
|
144
|
-
"current_testing_module_name": self.current_testing_module_name,
|
|
145
|
-
"current_testing_frid": self.current_testing_frid,
|
|
146
|
-
"fix_attempts": self.fix_attempts,
|
|
147
|
-
"execution_phase": self.execution_phase.name,
|
|
148
|
-
"acceptance_test_phase": self.acceptance_test_phase.name,
|
|
149
|
-
"acceptance_tests_completed": self.acceptance_tests_completed,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
142
|
|
|
153
143
|
@dataclass
|
|
154
144
|
class ScriptExecutionHistory:
|
system_config.py
CHANGED
|
@@ -29,12 +29,6 @@ class SystemConfig:
|
|
|
29
29
|
console.error(f"Failed to load system configuration: {e}")
|
|
30
30
|
sys.exit(69)
|
|
31
31
|
|
|
32
|
-
def get_error_message(self, message_key, **kwargs):
|
|
33
|
-
"""Get a formatted error message by its key."""
|
|
34
|
-
if message_key not in self.error_messages:
|
|
35
|
-
raise KeyError(f"Unknown error message key: {message_key}")
|
|
36
|
-
return self.error_messages[message_key]["message"].format(**kwargs)
|
|
37
|
-
|
|
38
32
|
|
|
39
33
|
# Create a singleton instance
|
|
40
34
|
system_config = SystemConfig()
|
tests/test_cli_output.py
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"""Unit tests for cli_output module."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
|
-
from io import StringIO
|
|
5
3
|
from unittest.mock import Mock, patch
|
|
6
4
|
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
5
|
from cli_output.status import (
|
|
10
6
|
_create_progress_bar,
|
|
11
7
|
_display_credit_line,
|
|
@@ -66,7 +62,7 @@ class TestDisplayCreditLine:
|
|
|
66
62
|
"type": "free",
|
|
67
63
|
"total": 50,
|
|
68
64
|
"remaining": 4,
|
|
69
|
-
"period_end": "
|
|
65
|
+
"period_end": "2028-12-01T00:00:00+00:00",
|
|
70
66
|
}
|
|
71
67
|
_display_credit_line(plan_credits)
|
|
72
68
|
|
|
@@ -74,7 +70,7 @@ class TestDisplayCreditLine:
|
|
|
74
70
|
call_args = mock_console.print.call_args[0][0]
|
|
75
71
|
assert "Free trial" in call_args
|
|
76
72
|
assert "4 of 50 remaining" in call_args
|
|
77
|
-
assert "expires
|
|
73
|
+
assert "expires Dec 1, 2028" in call_args
|
|
78
74
|
|
|
79
75
|
@patch("cli_output.status.console")
|
|
80
76
|
def test_display_active_pro_plan(self, mock_console):
|
|
@@ -114,13 +110,13 @@ class TestDisplayCreditLine:
|
|
|
114
110
|
"type": "free",
|
|
115
111
|
"total": 50,
|
|
116
112
|
"remaining": 0,
|
|
117
|
-
"period_end": "
|
|
113
|
+
"period_end": "2028-12-01T00:00:00+00:00",
|
|
118
114
|
}
|
|
119
115
|
_display_credit_line(plan_credits)
|
|
120
116
|
|
|
121
117
|
call_args = mock_console.print.call_args[0][0]
|
|
122
118
|
assert "0 of 50 remaining" in call_args
|
|
123
|
-
assert "expires
|
|
119
|
+
assert "expires Dec 1, 2028" in call_args
|
|
124
120
|
|
|
125
121
|
@patch("cli_output.status.console")
|
|
126
122
|
def test_timezone_naive_datetime(self, mock_console):
|
|
@@ -145,14 +141,14 @@ class TestDisplayPurchasedCreditLine:
|
|
|
145
141
|
bucket = {
|
|
146
142
|
"total": 100,
|
|
147
143
|
"remaining": 20,
|
|
148
|
-
"expiry_date": "
|
|
144
|
+
"expiry_date": "2028-12-12T00:00:00+00:00",
|
|
149
145
|
}
|
|
150
146
|
_display_purchased_credit_line(bucket)
|
|
151
147
|
|
|
152
148
|
call_args = mock_console.print.call_args[0][0]
|
|
153
149
|
assert "Purchased" in call_args
|
|
154
150
|
assert "20 of 100 remaining" in call_args
|
|
155
|
-
assert "expires
|
|
151
|
+
assert "expires Dec 12, 2028" in call_args
|
|
156
152
|
|
|
157
153
|
@patch("cli_output.status.console")
|
|
158
154
|
def test_display_expired_purchased_credits(self, mock_console):
|
|
@@ -189,7 +185,7 @@ class TestDisplayStatusMessage:
|
|
|
189
185
|
"""Test when user has active plan credits."""
|
|
190
186
|
plan_credits = {
|
|
191
187
|
"remaining": 10,
|
|
192
|
-
"period_end": "
|
|
188
|
+
"period_end": "2028-12-01T00:00:00+00:00",
|
|
193
189
|
}
|
|
194
190
|
_display_status_message(plan_credits, [])
|
|
195
191
|
|
|
@@ -215,7 +211,7 @@ class TestDisplayStatusMessage:
|
|
|
215
211
|
"""Test when user has no credits remaining."""
|
|
216
212
|
plan_credits = {
|
|
217
213
|
"remaining": 0,
|
|
218
|
-
"period_end": "
|
|
214
|
+
"period_end": "2028-12-01T00:00:00+00:00",
|
|
219
215
|
}
|
|
220
216
|
_display_status_message(plan_credits, [])
|
|
221
217
|
|
|
@@ -272,7 +268,7 @@ class TestPrintStatus:
|
|
|
272
268
|
"type": "free",
|
|
273
269
|
"total": 50,
|
|
274
270
|
"remaining": 10,
|
|
275
|
-
"period_end": "
|
|
271
|
+
"period_end": "2028-12-01T00:00:00+00:00",
|
|
276
272
|
},
|
|
277
273
|
"purchased_credits": [],
|
|
278
274
|
}
|
tests/test_plainfileparser.py
CHANGED
|
@@ -115,8 +115,8 @@ def test_reference_link_parsing(get_test_data_path):
|
|
|
115
115
|
[get_test_data_path("data/plainfile")],
|
|
116
116
|
)
|
|
117
117
|
asserted_resources = [
|
|
118
|
-
"task_list_ui_specification.yaml",
|
|
119
|
-
"add_new_task_modal_specification.yaml",
|
|
118
|
+
"tests/data/plainfile/task_list_ui_specification.yaml",
|
|
119
|
+
"tests/data/plainfile/add_new_task_modal_specification.yaml",
|
|
120
120
|
]
|
|
121
121
|
for functional_requirement in plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS]:
|
|
122
122
|
if "linked_resources" not in functional_requirement:
|
|
@@ -143,13 +143,13 @@ def test_reference_link_parsing(get_test_data_path):
|
|
|
143
143
|
assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][1]["linked_resources"] == [
|
|
144
144
|
{
|
|
145
145
|
"text": "task_list_ui_specification.yaml",
|
|
146
|
-
"target": "task_list_ui_specification.yaml",
|
|
146
|
+
"target": "tests/data/plainfile/task_list_ui_specification.yaml",
|
|
147
147
|
}
|
|
148
148
|
]
|
|
149
149
|
assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][2]["linked_resources"] == [
|
|
150
150
|
{
|
|
151
151
|
"text": "add_new_task_modal_specification.yaml",
|
|
152
|
-
"target": "add_new_task_modal_specification.yaml",
|
|
152
|
+
"target": "tests/data/plainfile/add_new_task_modal_specification.yaml",
|
|
153
153
|
}
|
|
154
154
|
]
|
|
155
155
|
|
tui/components.py
CHANGED
|
@@ -136,16 +136,10 @@ class ScriptOutputType(str, Enum):
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
class TUIComponents(str, Enum):
|
|
139
|
-
RENDER_MODULE_NAME_WIDGET = "render-module-name-widget"
|
|
140
|
-
RENDER_ID_WIDGET = "render-id-widget"
|
|
141
139
|
RENDER_STATUS_WIDGET = "render-status-widget"
|
|
142
|
-
UNIT_TEST_SCRIPT_OUTPUT_WIDGET = "unit-test-script-output-widget"
|
|
143
|
-
CONFORMANCE_TESTS_SCRIPT_OUTPUT_WIDGET = "conformance-tests-script-output-widget"
|
|
144
|
-
TESTING_ENVIRONMENT_SCRIPT_OUTPUT_WIDGET = "testing-environment-script-output-widget"
|
|
145
140
|
|
|
146
141
|
# FRID Progress widgets
|
|
147
142
|
FRID_PROGRESS = "frid-progress"
|
|
148
|
-
FRID_PROGRESS_HEADER = "frid-progress-header"
|
|
149
143
|
FRID_PROGRESS_RENDER_FR = "frid-progress-render-fr"
|
|
150
144
|
FRID_PROGRESS_UNIT_TEST = "frid-progress-unit-test"
|
|
151
145
|
FRID_PROGRESS_REFACTORING = "frid-progress-refactoring"
|
tui/plain2code_tui.py
CHANGED
|
@@ -70,6 +70,7 @@ class Plain2CodeTUI(App):
|
|
|
70
70
|
prepare_environment_script: str,
|
|
71
71
|
state_machine_version: str,
|
|
72
72
|
enter_pause_event: threading.Event | None = None,
|
|
73
|
+
on_cancel: Callable[[], None] | None = None,
|
|
73
74
|
default_log_level: str = "INFO",
|
|
74
75
|
**kwargs,
|
|
75
76
|
):
|
|
@@ -83,6 +84,7 @@ class Plain2CodeTUI(App):
|
|
|
83
84
|
self.prepare_environment_script: Optional[str] = prepare_environment_script
|
|
84
85
|
self.state_machine_version = state_machine_version
|
|
85
86
|
self.enter_pause_event = enter_pause_event
|
|
87
|
+
self._on_cancel = on_cancel
|
|
86
88
|
self.default_log_level = default_log_level
|
|
87
89
|
self._render_finished = False
|
|
88
90
|
|
|
@@ -311,4 +313,6 @@ class Plain2CodeTUI(App):
|
|
|
311
313
|
Note: Force exit may leave files partially written if interrupted during file I/O operations.
|
|
312
314
|
This is acceptable since the folders in which we are writing are git versioned and are reset in the next render.
|
|
313
315
|
"""
|
|
316
|
+
if not self._render_finished and self._on_cancel:
|
|
317
|
+
self._on_cancel()
|
|
314
318
|
self.exit()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
1
3
|
from textual import on
|
|
2
4
|
from textual.app import App, ComposeResult
|
|
3
5
|
from textual.binding import Binding
|
|
@@ -24,6 +26,7 @@ class PlainModuleRenderChoiceTUI(App):
|
|
|
24
26
|
render_choices: dict[str, RenderChoice],
|
|
25
27
|
state_machine_version: str,
|
|
26
28
|
render_id: str,
|
|
29
|
+
on_cancel: Optional[Callable[[], None]] = None,
|
|
27
30
|
**kwargs,
|
|
28
31
|
):
|
|
29
32
|
super().__init__(**kwargs)
|
|
@@ -33,6 +36,7 @@ class PlainModuleRenderChoiceTUI(App):
|
|
|
33
36
|
self.render_id = render_id
|
|
34
37
|
self._expandable_labels: list[dict] = []
|
|
35
38
|
self.render_choices = render_choices
|
|
39
|
+
self._on_cancel = on_cancel
|
|
36
40
|
|
|
37
41
|
def get_msg_from_choice(self, render_choice: RenderChoice) -> str:
|
|
38
42
|
if render_choice.choice_type == "module_start":
|
|
@@ -191,6 +195,11 @@ class PlainModuleRenderChoiceTUI(App):
|
|
|
191
195
|
self.selected_choice = self.render_choices[key]
|
|
192
196
|
self.exit(self.selected_choice)
|
|
193
197
|
|
|
198
|
+
def action_quit(self) -> None:
|
|
199
|
+
if self._on_cancel:
|
|
200
|
+
self._on_cancel()
|
|
201
|
+
self.exit()
|
|
202
|
+
|
|
194
203
|
async def action_copy_selection(self) -> None:
|
|
195
204
|
"""Handle ctrl+c: copy selected text if any.
|
|
196
205
|
|
tui/styles.css
CHANGED
tui/widget_helpers.py
CHANGED
|
@@ -67,24 +67,6 @@ def update_progress_item_status(tui, widget_id: str, status: str) -> None:
|
|
|
67
67
|
clear_progress_item_substates(tui, widget_id)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def update_widget_text(tui, widget_id: str, text: str) -> None:
|
|
71
|
-
"""Helper function to safely update a widget's text.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
tui: The Plain2CodeTUI instance
|
|
75
|
-
widget_id: The widget ID to update
|
|
76
|
-
text: The new text value
|
|
77
|
-
"""
|
|
78
|
-
try:
|
|
79
|
-
widget = tui.query_one(f"#{widget_id}")
|
|
80
|
-
if widget and hasattr(widget, "update"):
|
|
81
|
-
widget.update(text)
|
|
82
|
-
except NoMatches as e:
|
|
83
|
-
log_to_widget(tui, "WARNING", f"Widget {widget_id} not found: {e}")
|
|
84
|
-
except Exception as e:
|
|
85
|
-
log_to_widget(tui, "ERROR", f"Error updating widget {widget_id}: {e}")
|
|
86
|
-
|
|
87
|
-
|
|
88
70
|
def get_frid_progress(tui) -> FRIDProgress:
|
|
89
71
|
"""Helper function to safely get the FRIDProgress widget.
|
|
90
72
|
|
hash_key.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def hash_api_key(api_key):
|
|
6
|
-
"""Hash the provided API key using SHA-256 and return the hash as a hex string."""
|
|
7
|
-
try:
|
|
8
|
-
hash_object = hashlib.sha256(api_key.encode())
|
|
9
|
-
hex_dig = hash_object.hexdigest()
|
|
10
|
-
return hex_dig
|
|
11
|
-
except Exception as e:
|
|
12
|
-
error_message = f"An error occurred while hashing the API key: {str(e)}"
|
|
13
|
-
raise Exception(error_message)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
if len(sys.argv) != 2:
|
|
18
|
-
print("Error: Exactly one argument must be provided for the API key.")
|
|
19
|
-
print(f"Usage: python {sys.argv[0]} <api_key>")
|
|
20
|
-
sys.exit(1)
|
|
21
|
-
|
|
22
|
-
api_key = sys.argv[1]
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
hashed_key = hash_api_key(api_key)
|
|
26
|
-
print(f"Hashed API Key: {hashed_key}")
|
|
27
|
-
except Exception as e:
|
|
28
|
-
print(f"Error: {str(e)}")
|
|
29
|
-
sys.exit(1)
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
import file_utils
|
|
5
|
-
import plain_spec
|
|
6
|
-
from render_machine.render_types import ConformanceTestsRunningContext
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ConformanceTestHelpers:
|
|
10
|
-
@staticmethod
|
|
11
|
-
def fetch_existing_conformance_test_folder_names(conformance_tests_folder: str):
|
|
12
|
-
if os.path.isdir(conformance_tests_folder):
|
|
13
|
-
existing_folder_names = file_utils.list_folders_in_directory(conformance_tests_folder)
|
|
14
|
-
else:
|
|
15
|
-
# This happens if we're rendering the first FRID (without previously created conformance tests)
|
|
16
|
-
existing_folder_names = []
|
|
17
|
-
return existing_folder_names
|
|
18
|
-
|
|
19
|
-
@staticmethod
|
|
20
|
-
def fetch_existing_conformance_test_files(conformance_tests_running_context: ConformanceTestsRunningContext):
|
|
21
|
-
conformance_test_folder_name = ConformanceTestHelpers.get_current_conformance_test_folder_name(
|
|
22
|
-
conformance_tests_running_context
|
|
23
|
-
)
|
|
24
|
-
existing_conformance_test_files = file_utils.list_all_text_files(conformance_test_folder_name)
|
|
25
|
-
existing_conformance_test_files_content = file_utils.get_existing_files_content(
|
|
26
|
-
conformance_test_folder_name, existing_conformance_test_files
|
|
27
|
-
)
|
|
28
|
-
return existing_conformance_test_files, existing_conformance_test_files_content
|
|
29
|
-
|
|
30
|
-
@staticmethod
|
|
31
|
-
def current_conformance_tests_exist(conformance_tests_running_context: ConformanceTestsRunningContext) -> bool:
|
|
32
|
-
return (
|
|
33
|
-
conformance_tests_running_context.conformance_tests_json.get(
|
|
34
|
-
conformance_tests_running_context.current_testing_frid
|
|
35
|
-
)
|
|
36
|
-
is not None
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
def get_current_conformance_test_folder_name(
|
|
41
|
-
conformance_tests_running_context: ConformanceTestsRunningContext,
|
|
42
|
-
) -> str:
|
|
43
|
-
return conformance_tests_running_context.conformance_tests_json[
|
|
44
|
-
conformance_tests_running_context.current_testing_frid
|
|
45
|
-
]["folder_name"]
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def get_current_acceptance_tests(
|
|
49
|
-
conformance_tests_running_context: ConformanceTestsRunningContext,
|
|
50
|
-
) -> Optional[list[str]]:
|
|
51
|
-
if (
|
|
52
|
-
plain_spec.ACCEPTANCE_TESTS
|
|
53
|
-
in conformance_tests_running_context.conformance_tests_json[
|
|
54
|
-
conformance_tests_running_context.current_testing_frid
|
|
55
|
-
]
|
|
56
|
-
):
|
|
57
|
-
return conformance_tests_running_context.conformance_tests_json[
|
|
58
|
-
conformance_tests_running_context.current_testing_frid
|
|
59
|
-
][plain_spec.ACCEPTANCE_TESTS]
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
@staticmethod
|
|
63
|
-
def set_current_conformance_tests_summary(
|
|
64
|
-
conformance_tests_running_context: ConformanceTestsRunningContext, summary: list[dict]
|
|
65
|
-
):
|
|
66
|
-
conformance_tests_running_context.conformance_tests_json[
|
|
67
|
-
conformance_tests_running_context.current_testing_frid
|
|
68
|
-
]["test_summary"] = summary
|
|
File without changes
|
|
File without changes
|