codeplain 0.3.4__py3-none-any.whl → 0.3.6__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
- codeplain-0.3.6.dist-info/METADATA +144 -0
- {codeplain-0.3.4.dist-info → codeplain-0.3.6.dist-info}/RECORD +27 -27
- {codeplain-0.3.4.dist-info → codeplain-0.3.6.dist-info}/WHEEL +1 -1
- file_utils.py +13 -2
- plain2code.py +77 -36
- plain2code_exceptions.py +22 -0
- plain2code_logger.py +7 -0
- plain2code_state.py +1 -0
- plain2code_telemetry.py +2 -0
- plain2code_utils.py +18 -0
- plain_file.py +115 -14
- plain_spec.py +9 -0
- render_machine/actions/fix_conformance_test.py +23 -1
- render_machine/actions/prepare_testing_environment.py +6 -0
- render_machine/actions/refactor_code.py +18 -0
- render_machine/actions/render_functional_requirement.py +15 -1
- render_machine/render_context.py +0 -55
- render_machine/state_machine_config.py +7 -14
- tests/test_file_utils.py +53 -2
- tests/test_imports.py +11 -0
- tests/test_plain2code.py +94 -0
- tests/test_plainfileparser.py +149 -3
- tests/test_plainspec.py +36 -0
- tests/test_telemetry.py +16 -1
- codeplain-0.3.4.dist-info/METADATA +0 -149
- standard_template_library/__init__.py +0 -1
- {codeplain-0.3.4.dist-info → codeplain-0.3.6.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.4.dist-info → codeplain-0.3.6.dist-info}/licenses/LICENSE +0 -0
_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.6"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codeplain
|
|
3
|
+
Version: 0.3.6
|
|
4
|
+
Summary: Transform plain language specifications into working code
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Classifier: Environment :: Console
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
10
|
+
Requires-Python: ==3.11.*
|
|
11
|
+
Requires-Dist: gitpython==3.1.50
|
|
12
|
+
Requires-Dist: mistletoe==1.3.0
|
|
13
|
+
Requires-Dist: networkx==3.6.1
|
|
14
|
+
Requires-Dist: python-frontmatter==1.3.0
|
|
15
|
+
Requires-Dist: python-liquid2==0.4.0
|
|
16
|
+
Requires-Dist: pyyaml==6.0.3
|
|
17
|
+
Requires-Dist: requests==2.34.2
|
|
18
|
+
Requires-Dist: rich==15.0.0
|
|
19
|
+
Requires-Dist: sentry-sdk==2.63.0
|
|
20
|
+
Requires-Dist: textual>=7.5.0
|
|
21
|
+
Requires-Dist: tiktoken==0.13.0
|
|
22
|
+
Requires-Dist: transitions==0.9.3
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: black==26.3.1; extra == 'dev'
|
|
25
|
+
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: isort==5.13.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy==2.1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# *codeplain renderer
|
|
32
|
+
|
|
33
|
+
## What is \*codeplain?
|
|
34
|
+
|
|
35
|
+
\*codeplain platform makes coding agents better by having them write and maintain specifications instead of code. It extends coding agents with agentic skills for working at the specification layer, and with tools that convert specifications into tested and validated implementation code.
|
|
36
|
+
|
|
37
|
+
This repository is the renderer that powers that last step, turning \*\*\*plain specs into software using the *codeplain API.
|
|
38
|
+
|
|
39
|
+
The result is a software development workflow centered on specifications, which are faster and more efficient for AI to produce and easier for developers to review and maintain than raw code.
|
|
40
|
+
|
|
41
|
+
Schematic overview of the *codeplain's code rendering:
|
|
42
|
+
|
|
43
|
+
<img src="https://raw.githubusercontent.com/Codeplain-ai/plain2code_client/main/resources/codeplain_overview.png">
|
|
44
|
+
|
|
45
|
+
## Getting started
|
|
46
|
+
|
|
47
|
+
Get started in 15 minutes.
|
|
48
|
+
|
|
49
|
+
### 1. Install \*codeplain in the terminal
|
|
50
|
+
|
|
51
|
+
macOS and Linux:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
curl -fsSL https://codeplain.ai/install.sh | bash
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Windows PowerShell:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
irm https://codeplain.ai/install.ps1 | iex
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Windows users:** Please install WSL (Windows Subsystem for Linux) as this is currently the supported environment for running plain code on Windows.
|
|
64
|
+
|
|
65
|
+
Follow the installation instructions to complete setup. The installer installs the `codeplain` CLI, walks you through setting your `CODEPLAIN_API_KEY`, and offers to set up [plain-forge](#2-let-your-agent-work-in-specs-not-code) and the [plyn editor extension](#vs-code--cursor-extension) for you in the same session.
|
|
66
|
+
|
|
67
|
+
If you'd rather not run the installer, sign up at [platform.codeplain.ai](https://platform.codeplain.ai) to get your API key and export it yourself:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
export CODEPLAIN_API_KEY="your_actual_api_key_here"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Let your agent work in specs, not code
|
|
74
|
+
|
|
75
|
+
You describe what you want in plain English, an agent helps you write a spec in \*\*\*plain language, and \*codeplain renders the actual code. This is what [plain-forge](https://github.com/Codeplain-ai/plain-forge) sets up: a toolkit of skills, rules, and docs that plugs into your AI coding agent of choice — Claude Code, Codex, ForgeCode, OpenCode, or any other agent that reads from a standard skills directory — and turns a conversation into a complete `.plain` spec, then keeps maintaining it across the lifetime of the project.
|
|
76
|
+
|
|
77
|
+
If you didn't install it during step 1, run it any time:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx plain-forge install
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Convert specs into tested and validated code
|
|
84
|
+
|
|
85
|
+
\*codeplain extends coding agents with agentic skills for working at the specification layer, and with tools that convert specifications into tested and validated implementation code.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
codeplain your-project.plain
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Try it now
|
|
92
|
+
|
|
93
|
+
You can immediately test the system with a simple "Hello World" example:
|
|
94
|
+
|
|
95
|
+
- Change to the example folder and run the example:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd examples/example_hello_world_python
|
|
99
|
+
codeplain hello_world_python.plain
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
_Note: Rendering will take a few minutes to complete._
|
|
103
|
+
|
|
104
|
+
- The system will generate a Python application in the `build` directory. You can run it with:
|
|
105
|
+
```bash
|
|
106
|
+
cd build
|
|
107
|
+
python hello_world.py
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### VS Code / Cursor extension
|
|
111
|
+
|
|
112
|
+
If you're using Visual Studio Code or Cursor IDE, we also recommend installing the [plyn extension](https://marketplace.visualstudio.com/items?itemName=Codeplain.plyn) for `***plain` syntax highlighting. The installer in step 1 offers to install it automatically if it detects `code` or `cursor` on your `PATH`.
|
|
113
|
+
|
|
114
|
+
### Community and support
|
|
115
|
+
|
|
116
|
+
Share what you're building! Join the [\*codeplain Discord](https://discord.gg/cgbynb9hFq) if you have questions, ideas, or want to share what you built.
|
|
117
|
+
|
|
118
|
+
Hit an edge case or have feedback? Reach us at [support@codeplain.ai](mailto:support@codeplain.ai).
|
|
119
|
+
|
|
120
|
+
## Abstracting Away Code Generation Complexity with \*\*\*plain
|
|
121
|
+
|
|
122
|
+
\*\*\*plain is the language of spec-driven development — a specification language that combines the efficiency of natural language with the control and precision of code.
|
|
123
|
+
|
|
124
|
+
Structured sections for definitions, functional specs, implementation requirements, test requirements, and acceptance tests let your specs (not scattered docs or ad hoc prompts) become the source of truth for your software's behavior, abstracting away the complexity of using large language models for code generation.
|
|
125
|
+
|
|
126
|
+
Specs are plain text, so they fit into the workflows you already use — version control, pull requests, and code review.
|
|
127
|
+
|
|
128
|
+
See the [\*\*\*plain language guide](https://www.plainlang.org/docs/language-guide/) to learn the full syntax.
|
|
129
|
+
|
|
130
|
+
An example application in \*\*\*plain
|
|
131
|
+
|
|
132
|
+
<img src="resources/plain_example.png" width="70%" height="70%">
|
|
133
|
+
|
|
134
|
+
## Additional Resources
|
|
135
|
+
|
|
136
|
+
### Examples and Sample Projects
|
|
137
|
+
|
|
138
|
+
- See the [examples](examples) folder for sample projects in Golang, Python, React, and Flutter.
|
|
139
|
+
- For an example of how to implement Pong in \*\*\*plain, see the [pong](https://github.com/Codeplain-ai/plainlang-examples/tree/main/pong) example.
|
|
140
|
+
- For an example of how to implement a task manager in \*\*\*plain, see the [task-manager](https://github.com/Codeplain-ai/plainlang-examples/tree/main/task-manager) example.
|
|
141
|
+
|
|
142
|
+
### Documentation
|
|
143
|
+
|
|
144
|
+
- For more details on the ***plain format, see the [***plain language specification](https://www.plainlang.org/docs/).
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
_version.py,sha256=
|
|
1
|
+
_version.py,sha256=W_9dCm49nLvZulVAvvsafxLJjVBSKDBHz9K7szFZllo,22
|
|
2
2
|
change_detection.py,sha256=CazORZrezeZSw2I6VBvieN-c2SEjieZPo6pNrKwmMVA,6915
|
|
3
3
|
codeplain_REST_api.py,sha256=h_XoNws8fvf7OTtoVXhHh6OfLGg77UNFuggMBwaqBwE,21533
|
|
4
4
|
concept_utils.py,sha256=D4qVHFBRvfJZwiIq1AD_SV0Ev-RnOMg2yGWo6Cal7PM,7913
|
|
5
5
|
diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
|
|
6
6
|
event_bus.py,sha256=g5XLwRfjrvTIcB6urjREuCJzZj5TA93hy8lKXLGXuCE,650
|
|
7
|
-
file_utils.py,sha256=
|
|
7
|
+
file_utils.py,sha256=q-ApwiLLJCgmmoy6-nf1QM9DaFXwzCJek6B-jQTI9ak,11665
|
|
8
8
|
git_utils.py,sha256=LQQnL5DAFpH5fDBQgUZX2WfjWY4YC5OEGBPIXryTmnY,16896
|
|
9
9
|
memory_management.py,sha256=nHNudgaOqp5UGkIMi62TRCc2nUeFOM0w-I1FfxBFaQE,6120
|
|
10
10
|
module_renderer.py,sha256=pXMXKKGMhUwLLlSxppE_hpZlwpJ3WSHSfR9FQxt6rSI,6860
|
|
11
11
|
partial_rendering.py,sha256=0ChOaGytogBUEGISpbWkbjT9Hqy8pnOkJdJXcitJDgk,12219
|
|
12
|
-
plain2code.py,sha256=
|
|
12
|
+
plain2code.py,sha256=7VUE_q06VQinFHNfyh1I2u2Nt3EOa6WPkiPfqEF6x58,15452
|
|
13
13
|
plain2code_arguments.py,sha256=J05NwaY-O04rIWVF2yIXysNB_ofZS0wJFn0u1WaCCSA,15399
|
|
14
14
|
plain2code_console.py,sha256=YdE-Kz9qRAzIIAg7aYA1bnXRcwy_aTpbFtkR2zHbd-I,4918
|
|
15
15
|
plain2code_events.py,sha256=AM8KJS68uiVXRMLt861KaGhs2H4yzujlMB_xiYPfHQc,1478
|
|
16
|
-
plain2code_exceptions.py,sha256=
|
|
17
|
-
plain2code_logger.py,sha256=
|
|
16
|
+
plain2code_exceptions.py,sha256=wkkEXw2k4LCd09PIH3_Hu0HyZvtXYjFaA653Sfey4FA,2287
|
|
17
|
+
plain2code_logger.py,sha256=LdCaS29gWA7e1VhQijyXagsrFAnEi7VKU62b1dnlKnc,5114
|
|
18
18
|
plain2code_nodes.py,sha256=IwjQqJxRSJKgP6eEnkYGy3VQHIyoazgkFxAEkxkjfJ8,4426
|
|
19
19
|
plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,2235
|
|
20
|
-
plain2code_state.py,sha256=
|
|
21
|
-
plain2code_telemetry.py,sha256=
|
|
22
|
-
plain2code_utils.py,sha256=
|
|
23
|
-
plain_file.py,sha256=
|
|
20
|
+
plain2code_state.py,sha256=1VwyxgKdA13fAJNKSiPBfBb3A20TKfGCNnOm06YdcS4,2233
|
|
21
|
+
plain2code_telemetry.py,sha256=7yuTH7mQE_wGVW2H1oiVZ3UJg98ArB5HcUUm9isnv9g,4354
|
|
22
|
+
plain2code_utils.py,sha256=id04t9Iez5pYygrE3fKEyrcfWDcniKFT2iQoF2CzTJs,1409
|
|
23
|
+
plain_file.py,sha256=xdaDm5BL2dIhwMQeyq9nTXsugS4PDnR25CyrKBtHlLU,34386
|
|
24
24
|
plain_modules.py,sha256=uvRPfxBJwstaWi5lgYKgtQdhhf0Naiy2mS41tLw_TjY,16186
|
|
25
|
-
plain_spec.py,sha256=
|
|
25
|
+
plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
|
|
26
26
|
system_config.py,sha256=trgytfXKXoCn0Pgy9u00x2D0ZRHlh_tADlbkwZnF8QE,1038
|
|
27
27
|
cli_output/__init__.py,sha256=MBKl0YbVpWE-tBa3V5_aVbB7vbaqlIK2NsHTjRUZqis,284
|
|
28
28
|
cli_output/dry_run.py,sha256=6NKhNWPtD0CBK_QHSsSqIKLTzibTwxS9d8ltP9d1bgU,1473
|
|
@@ -36,10 +36,10 @@ render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
36
36
|
render_machine/code_renderer.py,sha256=-vp_ltU7e3ssX9hbS8IY4ETSp-39JAuoJ3ZltKpdt1M,3959
|
|
37
37
|
render_machine/conformance_tests.py,sha256=MeEveEGJTClLGxWw04P4TR6U9kX9Qgz9qtxNrB9a8QI,7085
|
|
38
38
|
render_machine/implementation_code_helpers.py,sha256=LdHNLoKgVtrDaGH2APX8C9vjNjLBUhu2V7OAaJvm4rM,2009
|
|
39
|
-
render_machine/render_context.py,sha256=
|
|
39
|
+
render_machine/render_context.py,sha256=fjU_S5W5HoAX3-WlEM8hp4DKMX_NDgVlfwQUAp0yaf8,25633
|
|
40
40
|
render_machine/render_types.py,sha256=cT3D0lmxQ3enIo0jGp3LEGCY2t3X1Q1_tqwcadzSAv0,8761
|
|
41
41
|
render_machine/render_utils.py,sha256=ao0e64VSOGYfb8hwJXIZlbqer5HjNMYJWHlLDoHnJyc,9123
|
|
42
|
-
render_machine/state_machine_config.py,sha256=
|
|
42
|
+
render_machine/state_machine_config.py,sha256=NnX5sBGkc6cNG8OOQ4PrJeJBCADRGZKJYQXV7ndR4bs,26789
|
|
43
43
|
render_machine/states.py,sha256=niFXqayyA_rezCrlzo0hYeHAruErcPzQVF-BeIBGnL8,1968
|
|
44
44
|
render_machine/triggers.py,sha256=VekNhHGR-u5JXIboqc_Eyqf73LNH7wezLK7r3WWtodk,1269
|
|
45
45
|
render_machine/actions/analyze_specification_ambiguity.py,sha256=SykZAozGvr1jySxbpKCgt11nMpZhOK0sN-J66M42T5Y,2847
|
|
@@ -49,17 +49,16 @@ render_machine/actions/commit_implementation_code_changes.py,sha256=yi51oAArLCgW
|
|
|
49
49
|
render_machine/actions/create_dist.py,sha256=xMPPyws7swVkaUSQg3dwMGScCi1BuII9y2C_XSP0N80,1067
|
|
50
50
|
render_machine/actions/exit_with_error.py,sha256=-LkQsFhmV-Nr741APTx38dqu473b6OSiIvKayFFsXxI,1270
|
|
51
51
|
render_machine/actions/finish_functional_requirement.py,sha256=usQgPSg6PHpBOR1RhK7lPz9zjY-2VJJDqi3T0nlqdLw,841
|
|
52
|
-
render_machine/actions/fix_conformance_test.py,sha256=
|
|
52
|
+
render_machine/actions/fix_conformance_test.py,sha256=jtVeL11J8K-a5YvoR3uW6TVytQZkiBgXlDyFR3wXYEs,10022
|
|
53
53
|
render_machine/actions/fix_unit_tests.py,sha256=hvwvCJDk_-8fHMpkNFyeBSG1IshIcufDpymF00ulPXY,2301
|
|
54
54
|
render_machine/actions/prepare_repositories.py,sha256=eE-uU6H_K5IQMraVWSRi4TKk_XPrnA878T_JwtI9cWo,3410
|
|
55
|
-
render_machine/actions/prepare_testing_environment.py,sha256=
|
|
56
|
-
render_machine/actions/refactor_code.py,sha256=
|
|
55
|
+
render_machine/actions/prepare_testing_environment.py,sha256=kIsXgV6-jOlqp-04cimPgTQoHPrVIu0zW-H_Jj-jowE,2106
|
|
56
|
+
render_machine/actions/refactor_code.py,sha256=gyPsnqgHseL2bkENu4WeGCRyV0R18_GH3JOrPdoxKvg,2609
|
|
57
57
|
render_machine/actions/render_conformance_tests.py,sha256=5gOQn-G1T9UWEI8sDfALcVdArk0ln9DgUprOPjccS-U,9268
|
|
58
|
-
render_machine/actions/render_functional_requirement.py,sha256=
|
|
58
|
+
render_machine/actions/render_functional_requirement.py,sha256=uiEwrMdArRdPX7eWysYMTI5y2pXacUySTpIgL2O-lOI,4602
|
|
59
59
|
render_machine/actions/run_conformance_tests.py,sha256=4sJTORl6YLFXQIdLMBQHDeJd2hztGyt3tsVvrCviBko,4008
|
|
60
60
|
render_machine/actions/run_unit_tests.py,sha256=A4WXtSzpCLDHYR8fQAbfxUf_E19GYflZSHZaSreoFPk,2101
|
|
61
61
|
render_machine/actions/summarize_conformance_tests.py,sha256=Vy1kRK8vyazK_BbsKuz1MLDN_TQWVxxzkwEEm-50byc,1639
|
|
62
|
-
standard_template_library/__init__.py,sha256=LtsHl0c3VV46DUInIqyOw4KKVoxKEMOx8dAZpMXtAXc,43
|
|
63
62
|
standard_template_library/golang-console-app-template.plain,sha256=JOusEjCj7jagnXUzaI-SdIr3hj1wTo2uShK017aNtVM,949
|
|
64
63
|
standard_template_library/python-console-app-template.plain,sha256=HW-REeF8Crk0r_w4-MxnleVHeW6PdOB2C8gO8FDG61g,931
|
|
65
64
|
standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOzWnDHdVpFj-XBTq6s4_2dy7LKV3lVkCzv8i4,114
|
|
@@ -68,17 +67,18 @@ tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
|
68
67
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
69
68
|
tests/test_change_detection.py,sha256=jH8SEhndX400DNqIKmcYLzxHqq9WFdgmuvY3XPq-ZFc,13534
|
|
70
69
|
tests/test_cli_output.py,sha256=owDP9pLHdErtPAr1paNKp1o044kuH-HxInHLIBLZQFI,18420
|
|
71
|
-
tests/test_file_utils.py,sha256
|
|
70
|
+
tests/test_file_utils.py,sha256=R_ZIhrTtwlhHxgmbBtA8PFEU5-QB4Fi5skJ7PS2KzdU,3614
|
|
72
71
|
tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
|
|
73
|
-
tests/test_imports.py,sha256=
|
|
72
|
+
tests/test_imports.py,sha256=1ncbqQhtQW237kIjyV-Ufl7-R-PuduvoYFj2iS-DXW4,2608
|
|
74
73
|
tests/test_partial_rendering.py,sha256=r0RUUd1pI4-2ZkRrM1j8_kUqv-70hrOGjuKBhZoRmH0,22758
|
|
74
|
+
tests/test_plain2code.py,sha256=DQYFc0JVSkMI8LZCNaQ__2Nph-DcZZW17rprJA54eyw,3851
|
|
75
75
|
tests/test_plain_modules.py,sha256=S7sd4ZYNotXOgCyZzmBd714QFLih69nlZ3tV8WNtVPo,13766
|
|
76
76
|
tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
|
|
77
|
-
tests/test_plainfileparser.py,sha256=
|
|
78
|
-
tests/test_plainspec.py,sha256=
|
|
77
|
+
tests/test_plainfileparser.py,sha256=kXWwaGKtYnTDn5yDoYTqumlwW0iSu441wxj3WZVQOP0,22807
|
|
78
|
+
tests/test_plainspec.py,sha256=ng55E8ajrH2-gPC1p_QDIe_zXplPvoqHJ-d57UWKFTw,3577
|
|
79
79
|
tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
|
|
80
80
|
tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
|
|
81
|
-
tests/test_telemetry.py,sha256=
|
|
81
|
+
tests/test_telemetry.py,sha256=hjm81aRQQZcZ0RB6aVbS3WO46_nIAii_csmbbtnUh74,7110
|
|
82
82
|
tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
|
|
83
83
|
tests/e2e/test_hello_world_python.py,sha256=PQy4elzB7WvpiWLKa6_HFO0K8HA6_6hGLaAqRi0J3OU,1627
|
|
84
84
|
tests/e2e/test_hello_world_python_windows.py,sha256=kJQZIuzj8OEEtgBVxnzFvyQJ_wvP2oBu9L-E9geg8sE,2010
|
|
@@ -91,8 +91,8 @@ tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
|
91
91
|
tui/state_handlers.py,sha256=4r_6m5YMcprQtgiTP0De3XFfGAF8VzgaWXHxPweYPbQ,16325
|
|
92
92
|
tui/styles.css,sha256=c13WY1nOgmtkT_FH9Ad2PjXrfi8LYwr6zzD6rBOh9tU,6685
|
|
93
93
|
tui/widget_helpers.py,sha256=mWDPrALsxLOzJg8KD9MXXafJKRiqG-EIp797jbOT77o,5972
|
|
94
|
-
codeplain-0.3.
|
|
95
|
-
codeplain-0.3.
|
|
96
|
-
codeplain-0.3.
|
|
97
|
-
codeplain-0.3.
|
|
98
|
-
codeplain-0.3.
|
|
94
|
+
codeplain-0.3.6.dist-info/METADATA,sha256=tX12onr0DJU5GgIbceMo3m8Cys7eptunE_ISO681FqA,6352
|
|
95
|
+
codeplain-0.3.6.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
96
|
+
codeplain-0.3.6.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
97
|
+
codeplain-0.3.6.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
|
|
98
|
+
codeplain-0.3.6.dist-info/RECORD,,
|
file_utils.py
CHANGED
|
@@ -8,8 +8,9 @@ from liquid2.exceptions import UndefinedError
|
|
|
8
8
|
|
|
9
9
|
import plain_spec
|
|
10
10
|
from plain2code_console import console
|
|
11
|
-
from plain2code_exceptions import UnsupportedResourceType
|
|
11
|
+
from plain2code_exceptions import UnsupportedBase64Content, UnsupportedResourceType
|
|
12
12
|
from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
|
|
13
|
+
from plain2code_utils import find_large_base64_blob
|
|
13
14
|
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER
|
|
14
15
|
|
|
15
16
|
BINARY_FILE_EXTENSIONS = [".pyc"]
|
|
@@ -157,7 +158,7 @@ def store_response_files(target_folder, response_files, existing_files):
|
|
|
157
158
|
|
|
158
159
|
os.makedirs(os.path.dirname(full_file_name), exist_ok=True)
|
|
159
160
|
|
|
160
|
-
with open(full_file_name, "w") as f:
|
|
161
|
+
with open(full_file_name, "w", encoding="utf-8") as f:
|
|
161
162
|
f.write(response_files[file_name])
|
|
162
163
|
|
|
163
164
|
if file_name not in existing_files:
|
|
@@ -206,6 +207,16 @@ def load_linked_resources(template_dirs: list[str], resources_list, module_name:
|
|
|
206
207
|
Please ensure that the resource exists in one of these locations, or specify the correct --template-dir if using custom templates.
|
|
207
208
|
""")
|
|
208
209
|
|
|
210
|
+
blob = find_large_base64_blob(content)
|
|
211
|
+
if blob is not None:
|
|
212
|
+
raise UnsupportedBase64Content(
|
|
213
|
+
f"Referenced resource '{file_name}' in module '{module_name}' contains a large "
|
|
214
|
+
f"base64-encoded blob ({len(blob)} characters), such as an embedded image. Inline "
|
|
215
|
+
"base64 data is not supported. Remove the data from the resource. "
|
|
216
|
+
"If the data should be used by the end software, "
|
|
217
|
+
"save the data to a separate file and include the file path in the specification without it being a reference file."
|
|
218
|
+
)
|
|
219
|
+
|
|
209
220
|
linked_resources[file_name] = content
|
|
210
221
|
|
|
211
222
|
return linked_resources
|
plain2code.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import importlib.resources
|
|
2
1
|
import logging
|
|
3
2
|
import logging.config
|
|
4
3
|
import os
|
|
@@ -26,9 +25,11 @@ from plain2code_events import RenderFailed
|
|
|
26
25
|
from plain2code_exceptions import (
|
|
27
26
|
ConflictingRequirements,
|
|
28
27
|
GitNotInstalledError,
|
|
28
|
+
ImportedModuleWithFunctionalitiesError,
|
|
29
29
|
InvalidAPIKey,
|
|
30
30
|
InvalidFridArgument,
|
|
31
31
|
MissingAPIKey,
|
|
32
|
+
MissingFunctionalitiesError,
|
|
32
33
|
MissingPreviousFunctionalitiesError,
|
|
33
34
|
MissingResource,
|
|
34
35
|
ModuleDoesNotExistError,
|
|
@@ -37,6 +38,7 @@ from plain2code_exceptions import (
|
|
|
37
38
|
PlainSyntaxError,
|
|
38
39
|
RenderCancelledError,
|
|
39
40
|
RenderingCreditBalanceTooLow,
|
|
41
|
+
UnsupportedBase64Content,
|
|
40
42
|
UnsupportedResourceType,
|
|
41
43
|
)
|
|
42
44
|
from plain2code_logger import (
|
|
@@ -54,7 +56,7 @@ from system_config import system_config
|
|
|
54
56
|
from tui.plain2code_tui import Plain2CodeTUI
|
|
55
57
|
from tui.plain_module_render_choice_tui import PlainModuleRenderChoiceTUI
|
|
56
58
|
|
|
57
|
-
DEFAULT_TEMPLATE_DIRS =
|
|
59
|
+
DEFAULT_TEMPLATE_DIRS = "standard_template_library"
|
|
58
60
|
RENDER_THREAD_SHUTDOWN_TIMEOUT = 0.7
|
|
59
61
|
|
|
60
62
|
# Exceptions that represent expected, user-facing error conditions. They are
|
|
@@ -65,6 +67,8 @@ EXPECTED_EXCEPTIONS = (
|
|
|
65
67
|
MissingResource,
|
|
66
68
|
TemplateNotFoundError,
|
|
67
69
|
PlainSyntaxError,
|
|
70
|
+
ImportedModuleWithFunctionalitiesError,
|
|
71
|
+
MissingFunctionalitiesError,
|
|
68
72
|
MissingPreviousFunctionalitiesError,
|
|
69
73
|
MissingAPIKey,
|
|
70
74
|
InvalidAPIKey,
|
|
@@ -74,6 +78,7 @@ EXPECTED_EXCEPTIONS = (
|
|
|
74
78
|
NetworkConnectionError,
|
|
75
79
|
ModuleDoesNotExistError,
|
|
76
80
|
UnsupportedResourceType,
|
|
81
|
+
UnsupportedBase64Content,
|
|
77
82
|
GitNotInstalledError,
|
|
78
83
|
SystemExit,
|
|
79
84
|
)
|
|
@@ -139,8 +144,8 @@ def setup_logging(
|
|
|
139
144
|
return logging.getLevelName(configured_log_level)
|
|
140
145
|
|
|
141
146
|
|
|
142
|
-
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
|
|
143
|
-
"""Check API connectivity
|
|
147
|
+
def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI) -> Optional[str]:
|
|
148
|
+
"""Check API connectivity, validate API key and client version, and return the user email."""
|
|
144
149
|
response = codeplainAPI.connection_check(system_config.client_version)
|
|
145
150
|
|
|
146
151
|
if not response.get("api_key_valid", False):
|
|
@@ -157,33 +162,58 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
|
|
|
157
162
|
"Please update using: uv tool upgrade codeplain"
|
|
158
163
|
)
|
|
159
164
|
|
|
165
|
+
return response.get("user_email")
|
|
160
166
|
|
|
161
|
-
def render(args, run_state: RunState, event_bus: EventBus, default_log_level: str = "INFO"): # noqa: C901
|
|
162
|
-
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
163
167
|
|
|
168
|
+
def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> None:
|
|
169
|
+
"""Warn when any loaded module (including required modules) defines acceptance tests
|
|
170
|
+
but no conformance tests script is configured.
|
|
171
|
+
|
|
172
|
+
Acceptance tests are treated as conformance tests, so a conformance tests script is required
|
|
173
|
+
to actually run them. Without it, the acceptance tests cannot be executed.
|
|
174
|
+
"""
|
|
175
|
+
if args.conformance_tests_script:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
module_names_with_acceptance_tests = [
|
|
179
|
+
module.module_name
|
|
180
|
+
for module in plain_module.all_required_modules + [plain_module]
|
|
181
|
+
if plain_spec.has_acceptance_tests(module.plain_source)
|
|
182
|
+
]
|
|
183
|
+
if not module_names_with_acceptance_tests:
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
module_names = ", ".join(module_names_with_acceptance_tests)
|
|
187
|
+
console.warning(
|
|
188
|
+
f"Acceptance tests were found ({module_names}) but no conformance tests script is configured. "
|
|
189
|
+
"Acceptance tests are treated as conformance tests and require a conformance tests script "
|
|
190
|
+
"(--conformance-tests-script or 'conformance_tests_script' in config) to be executed."
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def render( # noqa: C901
|
|
195
|
+
plain_module: plain_modules.PlainModule,
|
|
196
|
+
args,
|
|
197
|
+
run_state: RunState,
|
|
198
|
+
event_bus: EventBus,
|
|
199
|
+
default_log_level: str = "INFO",
|
|
200
|
+
):
|
|
164
201
|
# Compute render range from either --render-range or --render-from
|
|
165
202
|
render_range = None
|
|
166
203
|
if args.render_range or args.render_from:
|
|
167
|
-
|
|
168
|
-
_, plain_source, _ = plain_file.plain_file_parser(args.filename, template_dirs)
|
|
169
|
-
render_range = plain_spec.compute_render_range(args, plain_source)
|
|
204
|
+
render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
|
|
170
205
|
|
|
171
206
|
codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console)
|
|
172
207
|
assert args.api is not None and args.api != "", "API URL is required"
|
|
173
208
|
codeplainAPI.api_url = args.api
|
|
174
209
|
|
|
175
|
-
_check_connection(codeplainAPI)
|
|
210
|
+
run_state.user_email = _check_connection(codeplainAPI)
|
|
176
211
|
|
|
177
212
|
stop_event = threading.Event()
|
|
178
213
|
enter_pause_event = threading.Event()
|
|
179
214
|
signal.signal(signal.SIGTERM, lambda _signum, _frame: stop_event.set())
|
|
180
215
|
|
|
181
|
-
plain_module
|
|
182
|
-
args.filename,
|
|
183
|
-
args.build_folder,
|
|
184
|
-
args.conformance_tests_folder,
|
|
185
|
-
template_dirs,
|
|
186
|
-
)
|
|
216
|
+
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
|
|
187
217
|
|
|
188
218
|
render_choice = None
|
|
189
219
|
if render_range is None:
|
|
@@ -297,7 +327,7 @@ def main(): # noqa: C901
|
|
|
297
327
|
console.error(
|
|
298
328
|
"Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
|
|
299
329
|
)
|
|
300
|
-
|
|
330
|
+
sys.exit(1)
|
|
301
331
|
|
|
302
332
|
if not args.api:
|
|
303
333
|
args.api = "https://api.codeplain.ai"
|
|
@@ -306,30 +336,41 @@ def main(): # noqa: C901
|
|
|
306
336
|
print_status(args.api_key, args.api, system_config.client_version)
|
|
307
337
|
except Exception as e:
|
|
308
338
|
console.error(f"Error fetching status: {str(e)}")
|
|
339
|
+
sys.exit(1)
|
|
309
340
|
return
|
|
310
341
|
|
|
311
|
-
|
|
312
|
-
if args.dry_run or args.full_plain:
|
|
313
|
-
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
342
|
+
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
314
343
|
|
|
344
|
+
# Handle full plain early-exit (raw text dump; does not require a parsed module).
|
|
345
|
+
if args.full_plain:
|
|
315
346
|
try:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.info(full_plain_source)
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
if args.dry_run:
|
|
325
|
-
console.info("Printing dry run output...\n")
|
|
326
|
-
_, plain_source_tree, _ = plain_file.plain_file_parser(args.filename, template_dirs)
|
|
327
|
-
render_range = plain_spec.compute_render_range(args, plain_source_tree)
|
|
328
|
-
print_dry_run_output(plain_source_tree, render_range)
|
|
329
|
-
return
|
|
347
|
+
module_name = Path(args.filename).stem
|
|
348
|
+
plain_source = plain_file.read_module_plain_source(module_name, template_dirs)
|
|
349
|
+
[full_plain_source, _] = file_utils.get_loaded_templates(template_dirs, plain_source)
|
|
350
|
+
console.info("Full plain text:\n")
|
|
351
|
+
console.info(full_plain_source)
|
|
330
352
|
except Exception as e:
|
|
331
353
|
console.error(f"Error: {str(e)}")
|
|
332
|
-
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
# Parse the plain file (and its required modules) once; reused by dry-run and rendering.
|
|
357
|
+
try:
|
|
358
|
+
plain_module = plain_modules.PlainModule(
|
|
359
|
+
args.filename,
|
|
360
|
+
args.build_folder,
|
|
361
|
+
args.conformance_tests_folder,
|
|
362
|
+
template_dirs,
|
|
363
|
+
)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
console.error(f"Error: {str(e)}")
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
if args.dry_run:
|
|
369
|
+
console.info("Printing dry run output...\n")
|
|
370
|
+
render_range = plain_spec.compute_render_range(args, plain_module.plain_source)
|
|
371
|
+
print_dry_run_output(plain_module.plain_source, render_range)
|
|
372
|
+
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
|
|
373
|
+
return
|
|
333
374
|
|
|
334
375
|
event_bus = EventBus()
|
|
335
376
|
|
|
@@ -357,7 +398,7 @@ def main(): # noqa: C901
|
|
|
357
398
|
raise MissingAPIKey(
|
|
358
399
|
"Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
|
|
359
400
|
)
|
|
360
|
-
render(args, run_state, event_bus, default_log_level)
|
|
401
|
+
render(plain_module, args, run_state, event_bus, default_log_level)
|
|
361
402
|
except BaseException as e:
|
|
362
403
|
if isinstance(e, KeyboardInterrupt):
|
|
363
404
|
error_message = "Keyboard interrupt"
|
plain2code_exceptions.py
CHANGED
|
@@ -29,6 +29,11 @@ class PlainSyntaxError(Exception):
|
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class UnsupportedBase64Content(Exception):
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
32
37
|
class InternalClientError(Exception):
|
|
33
38
|
pass
|
|
34
39
|
|
|
@@ -73,6 +78,23 @@ class MissingPreviousFunctionalitiesError(Exception):
|
|
|
73
78
|
pass
|
|
74
79
|
|
|
75
80
|
|
|
81
|
+
class MissingFunctionalitiesError(Exception):
|
|
82
|
+
"""Raised when a module to be rendered has no functionalities specified at all."""
|
|
83
|
+
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ImportedModuleWithFunctionalitiesError(Exception):
|
|
88
|
+
"""Raised when a module brought in via ``import`` contains functional specs.
|
|
89
|
+
|
|
90
|
+
This is a usage error, not a syntax error: the module is syntactically valid
|
|
91
|
+
and would be fine as a render target, but functionalities are not allowed in
|
|
92
|
+
the import role.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
76
98
|
class NetworkConnectionError(Exception):
|
|
77
99
|
"""Raised when there is a network connectivity issue with the API server."""
|
|
78
100
|
|
plain2code_logger.py
CHANGED
|
@@ -9,6 +9,13 @@ from plain2code_state import RunState
|
|
|
9
9
|
|
|
10
10
|
LOGGER_NAME = "codeplain"
|
|
11
11
|
|
|
12
|
+
# Attach a NullHandler so that log records emitted before setup_logging() configures
|
|
13
|
+
# the real handlers (e.g. during --dry-run, --status, --full-plain, or early parse
|
|
14
|
+
# errors) are not printed to stderr by logging.lastResort. Without this, console.error()
|
|
15
|
+
# / console.warning() — which both log the message and print it via rich — would show the
|
|
16
|
+
# same message twice: once in plain text (from lastResort) and once styled (from rich).
|
|
17
|
+
logging.getLogger(LOGGER_NAME).addHandler(logging.NullHandler())
|
|
18
|
+
|
|
12
19
|
|
|
13
20
|
class IndentedFormatter(logging.Formatter):
|
|
14
21
|
def format(self, record):
|
plain2code_state.py
CHANGED
plain2code_telemetry.py
CHANGED
|
@@ -98,6 +98,8 @@ def capture_crash(exc_info, run_state: Optional[RunState], args) -> bool:
|
|
|
98
98
|
scope.set_tag("render_state", run_state.current_render_state)
|
|
99
99
|
scope.set_tag("current_module", run_state.current_module)
|
|
100
100
|
scope.set_tag("current_frid", run_state.current_frid)
|
|
101
|
+
if run_state.user_email:
|
|
102
|
+
scope.set_user({"email": run_state.user_email})
|
|
101
103
|
scope.set_tag("headless", bool(getattr(args, "headless", False)))
|
|
102
104
|
scope.set_tag("unittests_script_provided", bool(getattr(args, "unittests_script", None)))
|
|
103
105
|
scope.set_tag("conformance_tests_script_provided", bool(getattr(args, "conformance_tests_script", None)))
|
plain2code_utils.py
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
MAX_BASE64_BLOB_LENGTH = 8192
|
|
6
|
+
|
|
7
|
+
# Matches a long contiguous base64 / base64url run, optionally preceded by a data: URI header.
|
|
8
|
+
_BASE64_BLOB_PATTERN = re.compile(
|
|
9
|
+
r"(?:data:[\w.+-]+/[\w.+-]+;base64,)?[A-Za-z0-9+/_-]{%d,}={0,2}" % MAX_BASE64_BLOB_LENGTH
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_large_base64_blob(text: str) -> Optional[str]:
|
|
14
|
+
"""Return the first contiguous base64 blob at or above the threshold, or None."""
|
|
15
|
+
match = _BASE64_BLOB_PATTERN.search(text)
|
|
16
|
+
return match.group(0) if match else None
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
def format_duration_hms(total_seconds: int) -> str:
|
|
2
20
|
"""Format a duration in seconds as hours, minutes, and seconds (e.g. ``1h 2m 3.45s``, ``45.67s``)."""
|
|
3
21
|
if total_seconds < 0:
|