codeplain 0.3.5__py3-none-any.whl → 0.3.7__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.7.dist-info/METADATA +144 -0
- {codeplain-0.3.5.dist-info → codeplain-0.3.7.dist-info}/RECORD +23 -16
- {codeplain-0.3.5.dist-info → codeplain-0.3.7.dist-info}/WHEEL +1 -1
- file_utils.py +1 -1
- module_renderer.py +3 -2
- path_resolution.py +64 -0
- plain2code.py +11 -11
- plain2code_arguments.py +200 -99
- plain2code_console.py +2 -1
- plain2code_exceptions.py +17 -0
- plain2code_logger.py +17 -15
- plain_file.py +102 -42
- tests/test_arg_provenance.py +89 -0
- tests/test_folder_path_resolution.py +165 -0
- tests/test_imports.py +11 -0
- tests/test_log_file_name_resolution.py +89 -0
- tests/test_logging_config_path_resolution.py +67 -0
- tests/test_path_resolution.py +65 -0
- tests/test_plainfileparser.py +43 -5
- tests/test_script_path_resolution.py +107 -0
- codeplain-0.3.5.dist-info/METADATA +0 -149
- {codeplain-0.3.5.dist-info → codeplain-0.3.7.dist-info}/entry_points.txt +0 -0
- {codeplain-0.3.5.dist-info → codeplain-0.3.7.dist-info}/licenses/LICENSE +0 -0
_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.7"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codeplain
|
|
3
|
+
Version: 0.3.7
|
|
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.5.1
|
|
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.5.1; extra == 'dev'
|
|
25
|
+
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: isort==8.0.1; 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,26 +1,27 @@
|
|
|
1
|
-
_version.py,sha256=
|
|
1
|
+
_version.py,sha256=J0I0c7-a50EOnWXMryTu_E6xhXSYFBPjVpeYP_a3vRI,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=MNZ5li1ofIMOYB6FCrMa_UkX6nkGCgSZuaqzhqVc1Ok,11682
|
|
8
8
|
git_utils.py,sha256=LQQnL5DAFpH5fDBQgUZX2WfjWY4YC5OEGBPIXryTmnY,16896
|
|
9
9
|
memory_management.py,sha256=nHNudgaOqp5UGkIMi62TRCc2nUeFOM0w-I1FfxBFaQE,6120
|
|
10
|
-
module_renderer.py,sha256=
|
|
10
|
+
module_renderer.py,sha256=DZCIo_QzvhFw8EqQTKeF3HUbvfumrV2O1sz-gUhWpIA,6752
|
|
11
11
|
partial_rendering.py,sha256=0ChOaGytogBUEGISpbWkbjT9Hqy8pnOkJdJXcitJDgk,12219
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
path_resolution.py,sha256=0tMqU0a1g4DYhTOp7QjLVSEkv_cgasb4NI1mK809dUM,2233
|
|
13
|
+
plain2code.py,sha256=HGv3Or296pZRuqD5jcmuA3QnGl34aJD5f8619M7l5G4,15294
|
|
14
|
+
plain2code_arguments.py,sha256=3tAA9Haw-j97zztjXdhi6LxFtURP0Tfg9J_rvJDoXmw,17685
|
|
15
|
+
plain2code_console.py,sha256=6_PdrVBw9oDccqSGW_gVRYQjVgC_5W3ush4QT2_jGnw,4931
|
|
15
16
|
plain2code_events.py,sha256=AM8KJS68uiVXRMLt861KaGhs2H4yzujlMB_xiYPfHQc,1478
|
|
16
|
-
plain2code_exceptions.py,sha256=
|
|
17
|
-
plain2code_logger.py,sha256=
|
|
17
|
+
plain2code_exceptions.py,sha256=wkkEXw2k4LCd09PIH3_Hu0HyZvtXYjFaA653Sfey4FA,2287
|
|
18
|
+
plain2code_logger.py,sha256=RJNhesE9y59lPILvGhiJpAB4snTwfsHpG1Ir6EaPhiQ,4989
|
|
18
19
|
plain2code_nodes.py,sha256=IwjQqJxRSJKgP6eEnkYGy3VQHIyoazgkFxAEkxkjfJ8,4426
|
|
19
20
|
plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,2235
|
|
20
21
|
plain2code_state.py,sha256=1VwyxgKdA13fAJNKSiPBfBb3A20TKfGCNnOm06YdcS4,2233
|
|
21
22
|
plain2code_telemetry.py,sha256=7yuTH7mQE_wGVW2H1oiVZ3UJg98ArB5HcUUm9isnv9g,4354
|
|
22
23
|
plain2code_utils.py,sha256=id04t9Iez5pYygrE3fKEyrcfWDcniKFT2iQoF2CzTJs,1409
|
|
23
|
-
plain_file.py,sha256=
|
|
24
|
+
plain_file.py,sha256=k2rcq53q0kOf9_8egrZILpkrVkZdfJt7HU0gHBFskU4,35116
|
|
24
25
|
plain_modules.py,sha256=uvRPfxBJwstaWi5lgYKgtQdhhf0Naiy2mS41tLw_TjY,16186
|
|
25
26
|
plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
|
|
26
27
|
system_config.py,sha256=trgytfXKXoCn0Pgy9u00x2D0ZRHlh_tADlbkwZnF8QE,1038
|
|
@@ -65,19 +66,25 @@ standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOz
|
|
|
65
66
|
standard_template_library/typescript-react-app-template.plain,sha256=DN5wEJcT8ZqQTf0vFKi9Z1isi9cin2UVNOZb3r28d3M,715
|
|
66
67
|
tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
67
68
|
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
69
|
+
tests/test_arg_provenance.py,sha256=Vp-ZtNbTlSNOE8iiHOin3YI9sobbN0tRPUIfrYjxttQ,3459
|
|
68
70
|
tests/test_change_detection.py,sha256=jH8SEhndX400DNqIKmcYLzxHqq9WFdgmuvY3XPq-ZFc,13534
|
|
69
71
|
tests/test_cli_output.py,sha256=owDP9pLHdErtPAr1paNKp1o044kuH-HxInHLIBLZQFI,18420
|
|
70
72
|
tests/test_file_utils.py,sha256=R_ZIhrTtwlhHxgmbBtA8PFEU5-QB4Fi5skJ7PS2KzdU,3614
|
|
73
|
+
tests/test_folder_path_resolution.py,sha256=_ZvVFu5m2-AH8iQNn0fCYkA0flVxVwdh1oQiWcPNIxw,6611
|
|
71
74
|
tests/test_git_utils.py,sha256=h3rWhhs4XVRYqzVzdmj51siF_AYD-GkbZiefK2iGfWo,15614
|
|
72
|
-
tests/test_imports.py,sha256=
|
|
75
|
+
tests/test_imports.py,sha256=1ncbqQhtQW237kIjyV-Ufl7-R-PuduvoYFj2iS-DXW4,2608
|
|
76
|
+
tests/test_log_file_name_resolution.py,sha256=99g9NgPF-BZ98CvNyqDcltBAx4sLBE8gpQPAAieKiPc,3619
|
|
77
|
+
tests/test_logging_config_path_resolution.py,sha256=avz_ehRd18INwIXvAy1AwVzNNUz0vS8eu1nRe0bra8g,2650
|
|
73
78
|
tests/test_partial_rendering.py,sha256=r0RUUd1pI4-2ZkRrM1j8_kUqv-70hrOGjuKBhZoRmH0,22758
|
|
79
|
+
tests/test_path_resolution.py,sha256=yQpd-Unq1lwNv0wkRPwVs5pDT8JmyyDgbpiTQsyt--Y,2615
|
|
74
80
|
tests/test_plain2code.py,sha256=DQYFc0JVSkMI8LZCNaQ__2Nph-DcZZW17rprJA54eyw,3851
|
|
75
81
|
tests/test_plain_modules.py,sha256=S7sd4ZYNotXOgCyZzmBd714QFLih69nlZ3tV8WNtVPo,13766
|
|
76
82
|
tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
|
|
77
|
-
tests/test_plainfileparser.py,sha256=
|
|
83
|
+
tests/test_plainfileparser.py,sha256=xd9P0KqZJlJPn3RKT510IQbfBotk6nQ9O8p9iLwJiQk,22815
|
|
78
84
|
tests/test_plainspec.py,sha256=ng55E8ajrH2-gPC1p_QDIe_zXplPvoqHJ-d57UWKFTw,3577
|
|
79
85
|
tests/test_requires.py,sha256=6tnHYaKudxN-RiQjSFoi9nHe_x0ze3QF-f-j67zAE74,1250
|
|
80
86
|
tests/test_resolve_config_file.py,sha256=b87tDOvyzbsnE-QRWbZ97ou7NuyaAgskv-dbYBum31c,2830
|
|
87
|
+
tests/test_script_path_resolution.py,sha256=tiEqMevQRXr9YpfHN0xldOOQpLAUgnSqTzeZWkKHaUY,4156
|
|
81
88
|
tests/test_telemetry.py,sha256=hjm81aRQQZcZ0RB6aVbS3WO46_nIAii_csmbbtnUh74,7110
|
|
82
89
|
tests/e2e/conftest.py,sha256=5Dm67UTOE-M6hMb-mqZvwa6Ek_wmZgsR7fUv9ZEqCwY,4164
|
|
83
90
|
tests/e2e/test_hello_world_python.py,sha256=PQy4elzB7WvpiWLKa6_HFO0K8HA6_6hGLaAqRi0J3OU,1627
|
|
@@ -91,8 +98,8 @@ tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
|
91
98
|
tui/state_handlers.py,sha256=4r_6m5YMcprQtgiTP0De3XFfGAF8VzgaWXHxPweYPbQ,16325
|
|
92
99
|
tui/styles.css,sha256=c13WY1nOgmtkT_FH9Ad2PjXrfi8LYwr6zzD6rBOh9tU,6685
|
|
93
100
|
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.
|
|
101
|
+
codeplain-0.3.7.dist-info/METADATA,sha256=Gs_i0uMjyodZ11UAyrWIHyAp9vY4Zw_-todAZsYLRKg,6351
|
|
102
|
+
codeplain-0.3.7.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
103
|
+
codeplain-0.3.7.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
104
|
+
codeplain-0.3.7.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
|
|
105
|
+
codeplain-0.3.7.dist-info/RECORD,,
|
file_utils.py
CHANGED
|
@@ -293,7 +293,7 @@ def get_template_directories(plain_file_path, custom_template_dir=None, default_
|
|
|
293
293
|
3. Default template directory - for standard/fallback templates
|
|
294
294
|
"""
|
|
295
295
|
template_dirs = [
|
|
296
|
-
os.path.dirname(plain_file_path), # Highest precedence - directory containing plain file
|
|
296
|
+
os.path.dirname(os.path.abspath(plain_file_path)), # Highest precedence - directory containing plain file
|
|
297
297
|
]
|
|
298
298
|
|
|
299
299
|
if custom_template_dir:
|
module_renderer.py
CHANGED
|
@@ -158,13 +158,14 @@ class ModuleRenderer:
|
|
|
158
158
|
module.wipe_module()
|
|
159
159
|
|
|
160
160
|
self.loaded_modules = list[PlainModule]()
|
|
161
|
+
|
|
161
162
|
_, rendering_failed = self._render_module(self.plain_module, self.render_range, True)
|
|
163
|
+
|
|
162
164
|
if not rendering_failed:
|
|
163
|
-
# Get the last module that completed rendering
|
|
164
165
|
if self.args.copy_build:
|
|
165
166
|
rendered_code_path = f"{self.args.build_dest}/"
|
|
166
167
|
else:
|
|
167
|
-
rendered_code_path =
|
|
168
|
+
rendered_code_path = self.args.build_folder
|
|
168
169
|
|
|
169
170
|
self.run_state.set_render_generated_code_path(rendered_code_path)
|
|
170
171
|
self.event_bus.publish(RenderCompleted(rendered_code_path=rendered_code_path))
|
path_resolution.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Path resolution for CLI / config / default path arguments.
|
|
2
|
+
|
|
3
|
+
The rule, in one sentence: the resolution base for a relative path is determined
|
|
4
|
+
by *where the value was written*, not by which option key it sets.
|
|
5
|
+
|
|
6
|
+
- Values supplied on the command line resolve against the current working
|
|
7
|
+
directory (so they match what shell tab completion just produced).
|
|
8
|
+
- Values read from ``config.yaml`` resolve against the directory containing
|
|
9
|
+
that config file.
|
|
10
|
+
- Values left at their default (not supplied anywhere) resolve against the
|
|
11
|
+
directory containing the spec file.
|
|
12
|
+
|
|
13
|
+
Paths are expanded for ``~`` and returned as absolute paths. Canonicalization
|
|
14
|
+
(symlink resolution) is left to the caller, to be done just before I/O that
|
|
15
|
+
needs it (writing output, executing scripts) so we don't accidentally
|
|
16
|
+
dereference links the user wanted preserved.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
from typing import Literal, Optional
|
|
21
|
+
|
|
22
|
+
PathSource = Literal["cli", "config", "default"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def resolve_path(
|
|
26
|
+
value: str,
|
|
27
|
+
source: PathSource,
|
|
28
|
+
*,
|
|
29
|
+
cwd: str,
|
|
30
|
+
config_dir: Optional[str] = None,
|
|
31
|
+
spec_dir: str,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Resolve *value* to an absolute path using the base anchor for *source*.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
value: The path string as written by the user. May be absolute, relative,
|
|
37
|
+
or contain a leading ``~``.
|
|
38
|
+
source: Where the value came from -- ``"cli"``, ``"config"``, or
|
|
39
|
+
``"default"``.
|
|
40
|
+
cwd: Base directory for ``"cli"`` values.
|
|
41
|
+
config_dir: Base directory for ``"config"`` values. Must be provided
|
|
42
|
+
whenever ``source == "config"``.
|
|
43
|
+
spec_dir: Base directory for ``"default"`` values.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Absolute path with ``~`` expanded. Symlinks are not resolved.
|
|
47
|
+
"""
|
|
48
|
+
expanded = os.path.expanduser(value)
|
|
49
|
+
|
|
50
|
+
if os.path.isabs(expanded):
|
|
51
|
+
return os.path.normpath(expanded)
|
|
52
|
+
|
|
53
|
+
if source == "cli":
|
|
54
|
+
base = cwd
|
|
55
|
+
elif source == "config":
|
|
56
|
+
if config_dir is None:
|
|
57
|
+
raise ValueError("config_dir must be provided when source == 'config'")
|
|
58
|
+
base = config_dir
|
|
59
|
+
elif source == "default":
|
|
60
|
+
base = spec_dir
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Unknown path source: {source!r}")
|
|
63
|
+
|
|
64
|
+
return os.path.normpath(os.path.join(base, expanded))
|
plain2code.py
CHANGED
|
@@ -25,9 +25,11 @@ from plain2code_events import RenderFailed
|
|
|
25
25
|
from plain2code_exceptions import (
|
|
26
26
|
ConflictingRequirements,
|
|
27
27
|
GitNotInstalledError,
|
|
28
|
+
ImportedModuleWithFunctionalitiesError,
|
|
28
29
|
InvalidAPIKey,
|
|
29
30
|
InvalidFridArgument,
|
|
30
31
|
MissingAPIKey,
|
|
32
|
+
MissingFunctionalitiesError,
|
|
31
33
|
MissingPreviousFunctionalitiesError,
|
|
32
34
|
MissingResource,
|
|
33
35
|
ModuleDoesNotExistError,
|
|
@@ -46,7 +48,6 @@ from plain2code_logger import (
|
|
|
46
48
|
IndentedFormatter,
|
|
47
49
|
LoggingHandler,
|
|
48
50
|
dump_crash_logs,
|
|
49
|
-
get_log_file_path,
|
|
50
51
|
)
|
|
51
52
|
from plain2code_state import RunState
|
|
52
53
|
from plain2code_telemetry import capture_crash, initialize_telemetry
|
|
@@ -65,6 +66,8 @@ EXPECTED_EXCEPTIONS = (
|
|
|
65
66
|
MissingResource,
|
|
66
67
|
TemplateNotFoundError,
|
|
67
68
|
PlainSyntaxError,
|
|
69
|
+
ImportedModuleWithFunctionalitiesError,
|
|
70
|
+
MissingFunctionalitiesError,
|
|
68
71
|
MissingPreviousFunctionalitiesError,
|
|
69
72
|
MissingAPIKey,
|
|
70
73
|
InvalidAPIKey,
|
|
@@ -85,8 +88,7 @@ def setup_logging(
|
|
|
85
88
|
event_bus: EventBus,
|
|
86
89
|
run_state: RunState,
|
|
87
90
|
log_to_file: bool,
|
|
88
|
-
|
|
89
|
-
plain_file_path: Optional[str],
|
|
91
|
+
log_file_path: str,
|
|
90
92
|
headless: bool = False,
|
|
91
93
|
):
|
|
92
94
|
default_level = logging.DEBUG if args.verbose else logging.INFO
|
|
@@ -97,10 +99,9 @@ def setup_logging(
|
|
|
97
99
|
logging.getLogger("transitions").setLevel(logging.ERROR)
|
|
98
100
|
logging.getLogger("transitions.extensions.diagrams").setLevel(logging.ERROR)
|
|
99
101
|
|
|
100
|
-
log_file_path = get_log_file_path(plain_file_path, log_file_name)
|
|
101
|
-
|
|
102
102
|
# Try to load logging configuration from YAML file (takes precedence over --verbose)
|
|
103
103
|
config_loaded = False
|
|
104
|
+
|
|
104
105
|
if args.logging_config_path and os.path.exists(args.logging_config_path):
|
|
105
106
|
try:
|
|
106
107
|
with open(args.logging_config_path, "r") as f:
|
|
@@ -123,7 +124,7 @@ def setup_logging(
|
|
|
123
124
|
handler.setFormatter(formatter)
|
|
124
125
|
root_logger.addHandler(handler)
|
|
125
126
|
|
|
126
|
-
if log_to_file
|
|
127
|
+
if log_to_file:
|
|
127
128
|
try:
|
|
128
129
|
file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
|
|
129
130
|
file_handler.setFormatter(file_formatter)
|
|
@@ -323,7 +324,7 @@ def main(): # noqa: C901
|
|
|
323
324
|
console.error(
|
|
324
325
|
"Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
|
|
325
326
|
)
|
|
326
|
-
|
|
327
|
+
sys.exit(1)
|
|
327
328
|
|
|
328
329
|
if not args.api:
|
|
329
330
|
args.api = "https://api.codeplain.ai"
|
|
@@ -332,6 +333,7 @@ def main(): # noqa: C901
|
|
|
332
333
|
print_status(args.api_key, args.api, system_config.client_version)
|
|
333
334
|
except Exception as e:
|
|
334
335
|
console.error(f"Error fetching status: {str(e)}")
|
|
336
|
+
sys.exit(1)
|
|
335
337
|
return
|
|
336
338
|
|
|
337
339
|
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
@@ -351,7 +353,7 @@ def main(): # noqa: C901
|
|
|
351
353
|
# Parse the plain file (and its required modules) once; reused by dry-run and rendering.
|
|
352
354
|
try:
|
|
353
355
|
plain_module = plain_modules.PlainModule(
|
|
354
|
-
args.filename,
|
|
356
|
+
os.path.basename(args.filename),
|
|
355
357
|
args.build_folder,
|
|
356
358
|
args.conformance_tests_folder,
|
|
357
359
|
template_dirs,
|
|
@@ -378,9 +380,7 @@ def main(): # noqa: C901
|
|
|
378
380
|
# Suppress Rich console output.
|
|
379
381
|
console.quiet = True
|
|
380
382
|
|
|
381
|
-
default_log_level = setup_logging(
|
|
382
|
-
args, event_bus, run_state, args.log_to_file, args.log_file_name, args.filename, args.headless
|
|
383
|
-
)
|
|
383
|
+
default_log_level = setup_logging(args, event_bus, run_state, args.log_to_file, args.log_file_name, args.headless)
|
|
384
384
|
|
|
385
385
|
initialize_telemetry()
|
|
386
386
|
|