codeplain 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/METADATA +3 -3
- {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/RECORD +43 -43
- codeplain_REST_api.py +57 -43
- config/system_config.yaml +1 -16
- file_utils.py +5 -4
- git_utils.py +43 -13
- memory_management.py +1 -1
- module_renderer.py +114 -0
- plain2code.py +91 -30
- plain2code_console.py +3 -5
- plain2code_exceptions.py +26 -6
- plain2code_logger.py +11 -5
- plain2code_utils.py +7 -5
- plain_file.py +15 -37
- plain_modules.py +1 -4
- plain_spec.py +24 -6
- render_machine/actions/create_dist.py +1 -1
- render_machine/actions/exit_with_error.py +1 -1
- render_machine/actions/prepare_testing_environment.py +1 -1
- render_machine/actions/render_conformance_tests.py +2 -4
- render_machine/actions/render_functional_requirement.py +6 -6
- render_machine/actions/run_conformance_tests.py +3 -2
- render_machine/actions/run_unit_tests.py +1 -1
- render_machine/render_context.py +3 -3
- render_machine/render_utils.py +14 -6
- standard_template_library/golang-console-app-template.plain +2 -2
- standard_template_library/python-console-app-template.plain +2 -2
- standard_template_library/typescript-react-app-template.plain +2 -2
- system_config.py +3 -11
- tests/test_imports.py +2 -2
- tests/test_plainfile.py +2 -2
- tests/test_plainfileparser.py +10 -10
- tests/test_plainspec.py +2 -2
- tests/test_requires.py +2 -1
- tui/components.py +311 -103
- tui/plain2code_tui.py +101 -52
- tui/state_handlers.py +94 -47
- tui/styles.css +240 -52
- tui/widget_helpers.py +43 -47
- {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/WHEEL +0 -0
- {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/entry_points.txt +0 -0
- {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/licenses/LICENSE +0 -0
- /spinner.py → /tui/spinner.py +0 -0
plain2code.py
CHANGED
|
@@ -2,7 +2,7 @@ import importlib.resources
|
|
|
2
2
|
import logging
|
|
3
3
|
import logging.config
|
|
4
4
|
import os
|
|
5
|
-
import
|
|
5
|
+
import sys
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
@@ -17,7 +17,20 @@ from event_bus import EventBus
|
|
|
17
17
|
from module_renderer import ModuleRenderer
|
|
18
18
|
from plain2code_arguments import parse_arguments
|
|
19
19
|
from plain2code_console import console
|
|
20
|
-
from plain2code_exceptions import
|
|
20
|
+
from plain2code_exceptions import (
|
|
21
|
+
ConflictingRequirements,
|
|
22
|
+
CreditBalanceTooLow,
|
|
23
|
+
InternalServerError,
|
|
24
|
+
InvalidAPIKey,
|
|
25
|
+
InvalidFridArgument,
|
|
26
|
+
LLMInternalError,
|
|
27
|
+
MissingAPIKey,
|
|
28
|
+
MissingPreviousFunctionalitiesError,
|
|
29
|
+
MissingResource,
|
|
30
|
+
OutdatedClientVersion,
|
|
31
|
+
PlainSyntaxError,
|
|
32
|
+
UnexpectedState,
|
|
33
|
+
)
|
|
21
34
|
from plain2code_logger import (
|
|
22
35
|
CrashLogHandler,
|
|
23
36
|
IndentedFormatter,
|
|
@@ -46,10 +59,6 @@ UNRECOVERABLE_ERROR_EXIT_CODES = [69]
|
|
|
46
59
|
TIMEOUT_ERROR_EXIT_CODE = 124
|
|
47
60
|
|
|
48
61
|
|
|
49
|
-
class InvalidFridArgument(Exception):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
|
|
53
62
|
def get_render_range(render_range, plain_source):
|
|
54
63
|
render_range = render_range.split(",")
|
|
55
64
|
range_end = render_range[1] if len(render_range) == 2 else render_range[0]
|
|
@@ -93,6 +102,7 @@ def setup_logging(
|
|
|
93
102
|
log_to_file: bool,
|
|
94
103
|
log_file_name: str,
|
|
95
104
|
plain_file_path: Optional[str],
|
|
105
|
+
render_id: str,
|
|
96
106
|
):
|
|
97
107
|
# Set default level to INFO for everything not explicitly configured
|
|
98
108
|
logging.getLogger().setLevel(logging.INFO)
|
|
@@ -152,11 +162,29 @@ def setup_logging(
|
|
|
152
162
|
crash_handler.setFormatter(formatter)
|
|
153
163
|
root_logger.addHandler(crash_handler)
|
|
154
164
|
|
|
165
|
+
root_logger.info(f"Render ID: {render_id}") # Ensure render ID is logged in to codeplain.log file
|
|
155
166
|
|
|
156
|
-
def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # noqa: C901
|
|
157
|
-
# Check system requirements before proceeding
|
|
158
|
-
system_config.verify_requirements()
|
|
159
167
|
|
|
168
|
+
def _check_connection(codeplainAPI):
|
|
169
|
+
"""Check API connectivity and validate API key and client version."""
|
|
170
|
+
response = codeplainAPI.connection_check(system_config.client_version)
|
|
171
|
+
|
|
172
|
+
if not response.get("api_key_valid", False):
|
|
173
|
+
raise InvalidAPIKey(
|
|
174
|
+
"Provided API key is invalid. Please provide a valid API key using the CODEPLAIN_API_KEY environment variable "
|
|
175
|
+
"or the --api-key argument."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if not response.get("client_version_valid", False):
|
|
179
|
+
min_version = response.get("min_client_version", "unknown")
|
|
180
|
+
raise OutdatedClientVersion(
|
|
181
|
+
f"Your client version ({system_config.client_version}) is outdated. Minimum required version is {min_version}. "
|
|
182
|
+
"Please update using:"
|
|
183
|
+
" uv tool upgrade codeplain"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # noqa: C901
|
|
160
188
|
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
|
|
161
189
|
|
|
162
190
|
console.info(f"Rendering {args.filename} to target code.")
|
|
@@ -177,6 +205,8 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no
|
|
|
177
205
|
assert args.api is not None and args.api != "", "API URL is required"
|
|
178
206
|
codeplainAPI.api_url = args.api
|
|
179
207
|
|
|
208
|
+
_check_connection(codeplainAPI)
|
|
209
|
+
|
|
180
210
|
module_renderer = ModuleRenderer(
|
|
181
211
|
codeplainAPI,
|
|
182
212
|
args.filename,
|
|
@@ -194,6 +224,7 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no
|
|
|
194
224
|
unittests_script=args.unittests_script,
|
|
195
225
|
conformance_tests_script=args.conformance_tests_script,
|
|
196
226
|
prepare_environment_script=args.prepare_environment_script,
|
|
227
|
+
state_machine_version=system_config.client_version,
|
|
197
228
|
css_path="styles.css",
|
|
198
229
|
)
|
|
199
230
|
result = app.run()
|
|
@@ -206,18 +237,19 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no
|
|
|
206
237
|
return
|
|
207
238
|
|
|
208
239
|
|
|
209
|
-
def main():
|
|
240
|
+
def main(): # noqa: C901
|
|
210
241
|
args = parse_arguments()
|
|
211
242
|
|
|
212
243
|
event_bus = EventBus()
|
|
213
244
|
|
|
214
|
-
setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename)
|
|
215
|
-
|
|
216
245
|
if not args.api:
|
|
217
246
|
args.api = "https://api.codeplain.ai"
|
|
218
247
|
|
|
219
248
|
run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with)
|
|
220
249
|
|
|
250
|
+
setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename, run_state.render_id)
|
|
251
|
+
|
|
252
|
+
exc_info = None
|
|
221
253
|
try:
|
|
222
254
|
# Validate API key is present
|
|
223
255
|
if not args.api_key:
|
|
@@ -225,42 +257,71 @@ def main():
|
|
|
225
257
|
"API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument."
|
|
226
258
|
)
|
|
227
259
|
|
|
260
|
+
console.debug(f"Render ID: {run_state.render_id}") # Ensure render ID is logged to the console
|
|
228
261
|
render(args, run_state, codeplain_api, event_bus)
|
|
229
262
|
except InvalidFridArgument as e:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
# see the render ID that's printed at the very start of the rendering process.
|
|
233
|
-
dump_crash_logs(args)
|
|
263
|
+
exc_info = sys.exc_info()
|
|
264
|
+
console.error(f"Invalid FRID argument: {str(e)}.\n")
|
|
234
265
|
except FileNotFoundError as e:
|
|
235
|
-
|
|
266
|
+
exc_info = sys.exc_info()
|
|
267
|
+
console.error(f"File not found: {str(e)}\n")
|
|
236
268
|
console.debug(f"Render ID: {run_state.render_id}")
|
|
237
|
-
dump_crash_logs(args)
|
|
238
|
-
except plain_file.InvalidPlainFileExtension as e:
|
|
239
|
-
console.error(f"Error rendering plain code: {str(e)}\n")
|
|
240
|
-
dump_crash_logs(args)
|
|
241
269
|
except TemplateNotFoundError as e:
|
|
242
|
-
|
|
270
|
+
exc_info = sys.exc_info()
|
|
271
|
+
console.error(f"Template not found: {str(e)}\n")
|
|
243
272
|
console.error(system_config.get_error_message("template_not_found"))
|
|
244
|
-
dump_crash_logs(args)
|
|
245
273
|
except PlainSyntaxError as e:
|
|
246
|
-
|
|
247
|
-
|
|
274
|
+
exc_info = sys.exc_info()
|
|
275
|
+
console.error(f"Plain syntax error: {str(e)}\n")
|
|
248
276
|
except KeyboardInterrupt:
|
|
277
|
+
exc_info = sys.exc_info()
|
|
249
278
|
console.error("Keyboard interrupt")
|
|
250
|
-
# Don't print the traceback here because it's going to be from keyboard interrupt and we don't really care about that
|
|
251
279
|
console.debug(f"Render ID: {run_state.render_id}")
|
|
252
|
-
dump_crash_logs(args)
|
|
253
280
|
except RequestException as e:
|
|
281
|
+
exc_info = sys.exc_info()
|
|
282
|
+
console.error(f"Error rendering plain code: {str(e)}\n")
|
|
283
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
284
|
+
except MissingPreviousFunctionalitiesError as e:
|
|
285
|
+
exc_info = sys.exc_info()
|
|
254
286
|
console.error(f"Error rendering plain code: {str(e)}\n")
|
|
255
287
|
console.debug(f"Render ID: {run_state.render_id}")
|
|
256
|
-
dump_crash_logs(args)
|
|
257
288
|
except MissingAPIKey as e:
|
|
258
289
|
console.error(f"Missing API key: {str(e)}\n")
|
|
290
|
+
except InvalidAPIKey as e:
|
|
291
|
+
console.error(f"Invalid API key: {str(e)}\n")
|
|
292
|
+
except OutdatedClientVersion as e:
|
|
293
|
+
console.error(f"Outdated client version: {str(e)}\n")
|
|
294
|
+
except (InternalServerError, UnexpectedState):
|
|
295
|
+
exc_info = sys.exc_info()
|
|
296
|
+
console.error(
|
|
297
|
+
f"Internal server error.\n\nPlease report the error to support@codeplain.ai with the attached {args.log_file_name} file."
|
|
298
|
+
)
|
|
299
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
300
|
+
except ConflictingRequirements as e:
|
|
301
|
+
exc_info = sys.exc_info()
|
|
302
|
+
console.error(f"Conflicting requirements: {str(e)}\n")
|
|
303
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
304
|
+
except CreditBalanceTooLow as e:
|
|
305
|
+
exc_info = sys.exc_info()
|
|
306
|
+
console.error(f"Credit balance too low: {str(e)}\n")
|
|
307
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
308
|
+
except LLMInternalError as e:
|
|
309
|
+
exc_info = sys.exc_info()
|
|
310
|
+
console.error(f"LLM internal error: {str(e)}\n")
|
|
311
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
312
|
+
except MissingResource as e:
|
|
313
|
+
exc_info = sys.exc_info()
|
|
314
|
+
console.error(f"Missing resource: {str(e)}\n")
|
|
315
|
+
console.debug(f"Render ID: {run_state.render_id}")
|
|
259
316
|
except Exception as e:
|
|
317
|
+
exc_info = sys.exc_info()
|
|
260
318
|
console.error(f"Error rendering plain code: {str(e)}\n")
|
|
261
319
|
console.debug(f"Render ID: {run_state.render_id}")
|
|
262
|
-
|
|
263
|
-
|
|
320
|
+
finally:
|
|
321
|
+
if exc_info:
|
|
322
|
+
# Log traceback using the logging system
|
|
323
|
+
logging.error("Render crashed with exception:", exc_info=exc_info)
|
|
324
|
+
dump_crash_logs(args)
|
|
264
325
|
|
|
265
326
|
|
|
266
327
|
if __name__ == "__main__": # noqa: C901
|
plain2code_console.py
CHANGED
|
@@ -52,7 +52,7 @@ class Plain2CodeConsole(Console):
|
|
|
52
52
|
return
|
|
53
53
|
|
|
54
54
|
tree = self._create_tree_from_files(root_folder, files)
|
|
55
|
-
super().print(f"\n
|
|
55
|
+
super().print(f"\n{header}", style=style)
|
|
56
56
|
|
|
57
57
|
super().print(tree, style=style)
|
|
58
58
|
|
|
@@ -87,9 +87,7 @@ class Plain2CodeConsole(Console):
|
|
|
87
87
|
else:
|
|
88
88
|
file_lines = len(content.splitlines())
|
|
89
89
|
file_tokens = len(self.llm_encoding.encode(content))
|
|
90
|
-
current_level = current_level.add(
|
|
91
|
-
f"{part} [b]({file_lines} lines, {file_tokens} tokens)[/b]"
|
|
92
|
-
)
|
|
90
|
+
current_level = current_level.add(f"{part} ({file_lines} lines, {file_tokens} tokens)")
|
|
93
91
|
else:
|
|
94
92
|
current_level = current_level.add(part)
|
|
95
93
|
else:
|
|
@@ -107,7 +105,7 @@ class Plain2CodeConsole(Console):
|
|
|
107
105
|
if resource_name["target"] in linked_resources:
|
|
108
106
|
file_tokens = len(self.llm_encoding.encode(linked_resources[resource_name["target"]]))
|
|
109
107
|
self.input(
|
|
110
|
-
f"- {resource_name['text']} [
|
|
108
|
+
f"- {resource_name['text']} [#4169E1]({resource_name['target']}, {file_tokens} tokens)[/#4169E1]"
|
|
111
109
|
)
|
|
112
110
|
|
|
113
111
|
self.input()
|
plain2code_exceptions.py
CHANGED
|
@@ -25,25 +25,45 @@ class PlainSyntaxError(Exception):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class UnexpectedState(Exception):
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class
|
|
32
|
+
class MissingAPIKey(Exception):
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class
|
|
36
|
+
class InvalidAPIKey(Exception):
|
|
37
37
|
pass
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class
|
|
40
|
+
class OutdatedClientVersion(Exception):
|
|
41
41
|
pass
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
class
|
|
44
|
+
class InvalidFridArgument(Exception):
|
|
45
45
|
pass
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
class
|
|
48
|
+
class InvalidGitRepositoryError(Exception):
|
|
49
|
+
"""Raised when the git repository is in an invalid state."""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class InvalidLiquidVariableName(Exception):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ModuleDoesNotExistError(Exception):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InternalServerError(Exception):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MissingPreviousFunctionalitiesError(Exception):
|
|
67
|
+
"""Raised when trying to render from a FRID but previous FRID commits are missing."""
|
|
68
|
+
|
|
49
69
|
pass
|
plain2code_logger.py
CHANGED
|
@@ -4,7 +4,6 @@ import time
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from event_bus import EventBus
|
|
7
|
-
from plain2code_console import console
|
|
8
7
|
from plain2code_events import LogMessageEmitted
|
|
9
8
|
|
|
10
9
|
|
|
@@ -42,6 +41,7 @@ class TuiLoggingHandler(logging.Handler):
|
|
|
42
41
|
super().__init__()
|
|
43
42
|
self.event_bus = event_bus
|
|
44
43
|
self._buffer = []
|
|
44
|
+
self.start_time = time.time() # Record start time for offset calculation
|
|
45
45
|
|
|
46
46
|
# Register to be notified when event bus is ready
|
|
47
47
|
self.event_bus.on_ready(self._flush_buffer)
|
|
@@ -49,8 +49,11 @@ class TuiLoggingHandler(logging.Handler):
|
|
|
49
49
|
def emit(self, record):
|
|
50
50
|
try:
|
|
51
51
|
# Extract structured data from the log record
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
offset_seconds = record.created - self.start_time
|
|
53
|
+
minutes = int(offset_seconds // 60)
|
|
54
|
+
seconds = int(offset_seconds % 60)
|
|
55
|
+
milliseconds = int((offset_seconds % 1) * 100)
|
|
56
|
+
timestamp = f"{minutes:02d}:{seconds:02d}:{milliseconds:02d}"
|
|
54
57
|
event = LogMessageEmitted(
|
|
55
58
|
logger_name=record.name,
|
|
56
59
|
level=record.levelname,
|
|
@@ -63,6 +66,10 @@ class TuiLoggingHandler(logging.Handler):
|
|
|
63
66
|
self.event_bus.publish(event)
|
|
64
67
|
else:
|
|
65
68
|
self._buffer.append(event)
|
|
69
|
+
except RuntimeError:
|
|
70
|
+
# We're going to get this crash after the TUI app is closed (forcefully).
|
|
71
|
+
# NOTE: This should be more thought out.
|
|
72
|
+
pass
|
|
66
73
|
except Exception:
|
|
67
74
|
self.handleError(record)
|
|
68
75
|
|
|
@@ -123,5 +130,4 @@ def dump_crash_logs(args, formatter=None):
|
|
|
123
130
|
if crash_handler and args.filename:
|
|
124
131
|
log_file_path = get_log_file_path(args.filename, args.log_file_name)
|
|
125
132
|
|
|
126
|
-
|
|
127
|
-
console.error(f"\nLogs have been dumped to {log_file_path}")
|
|
133
|
+
crash_handler.dump_to_file(log_file_path, formatter)
|
plain2code_utils.py
CHANGED
|
@@ -24,15 +24,17 @@ def print_dry_run_output(plain_source_tree: dict, render_range: Optional[list[st
|
|
|
24
24
|
console.info(
|
|
25
25
|
"-------------------------------------"
|
|
26
26
|
f"Rendering functional requirement {frid}"
|
|
27
|
-
f"
|
|
27
|
+
f"{functional_requirement_text}"
|
|
28
28
|
"-------------------------------------"
|
|
29
29
|
)
|
|
30
30
|
if plain_spec.ACCEPTANCE_TESTS in specifications:
|
|
31
31
|
for i, acceptance_test in enumerate(specifications[plain_spec.ACCEPTANCE_TESTS], 1):
|
|
32
|
-
console.info(f"
|
|
32
|
+
console.info(f"Generating acceptance test #{i}:\n\n{acceptance_test}")
|
|
33
33
|
else:
|
|
34
|
-
console.info(
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
console.info(
|
|
35
|
+
"-------------------------------------\n"
|
|
36
|
+
f"Skipping rendering iteration: {frid}\n"
|
|
37
|
+
"-------------------------------------"
|
|
38
|
+
)
|
|
37
39
|
|
|
38
40
|
frid = plain_spec.get_next_frid(plain_source_tree, frid)
|
plain_file.py
CHANGED
|
@@ -44,22 +44,6 @@ class PlainFileParseResult:
|
|
|
44
44
|
required_concepts: list[str]
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class OnlyRelativeLinksAllowed(Exception):
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class LinkMustHaveTextSpecified(Exception):
|
|
52
|
-
pass
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class InvalidPlainFileExtension(Exception):
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class PlainModuleNotFound(Exception):
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
|
|
63
47
|
class PlainRenderer(MarkdownRenderer):
|
|
64
48
|
def render_link(self, token: Link) -> Iterable[Fragment]:
|
|
65
49
|
yield from self.embed_span(
|
|
@@ -95,12 +79,12 @@ def check_section_for_linked_resources(section):
|
|
|
95
79
|
for link in traverse(section, klass=Link):
|
|
96
80
|
parsed_url = urlparse(link.node.target)
|
|
97
81
|
if parsed_url.scheme != "" or os.path.isabs(link.node.target):
|
|
98
|
-
raise
|
|
82
|
+
raise PlainSyntaxError(
|
|
99
83
|
f"Only relative links are allowed (text: {link.node.children[0].content}, target: {link.node.target})."
|
|
100
84
|
)
|
|
101
85
|
|
|
102
86
|
if len(link.node.children) != 1:
|
|
103
|
-
raise
|
|
87
|
+
raise PlainSyntaxError(f"Link must have text specified (link: {link.node.target}).")
|
|
104
88
|
|
|
105
89
|
linked_resources.append({"text": link.node.children[0].content, "target": link.node.target})
|
|
106
90
|
|
|
@@ -222,7 +206,7 @@ def _process_single_acceptance_test_requirement(functional_requirement: mistleto
|
|
|
222
206
|
# Handle the case when the heading is not valid. This case includes cases such as:
|
|
223
207
|
# - Writing `acceptance test` instead of `acceptance tests` (or any other syntax diffs).
|
|
224
208
|
# - Instead of specifying `acceptance tests` below the functional requirement, creator of the plain file
|
|
225
|
-
# might have specified some other building block (e.g. `
|
|
209
|
+
# might have specified some other building block (e.g. `implementation reqs`)
|
|
226
210
|
raise PlainSyntaxError(acceptance_test_heading_problem)
|
|
227
211
|
|
|
228
212
|
if is_acceptance_test_heading:
|
|
@@ -529,7 +513,7 @@ def parse_plain_source( # noqa: C901
|
|
|
529
513
|
def read_module_plain_source(module_name: str, template_dirs: list[str]) -> str:
|
|
530
514
|
plain_source_text = file_utils.open_from(template_dirs, module_name + PLAIN_SOURCE_FILE_EXTENSION)
|
|
531
515
|
if plain_source_text is None:
|
|
532
|
-
raise
|
|
516
|
+
raise PlainSyntaxError(f"Module does not exist ({module_name}).")
|
|
533
517
|
return plain_source_text
|
|
534
518
|
|
|
535
519
|
|
|
@@ -566,12 +550,9 @@ def process_required_modules(
|
|
|
566
550
|
if len(all_required_modules) > 0 and module_name == all_required_modules[-1]:
|
|
567
551
|
continue
|
|
568
552
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
)
|
|
573
|
-
except PlainModuleNotFound:
|
|
574
|
-
raise PlainSyntaxError(f"Required module not found ({module_name}).")
|
|
553
|
+
plain_file_parse_result = parse_plain_file(
|
|
554
|
+
module_name, code_variables, template_dirs, imported_modules=[], modules_trace=[]
|
|
555
|
+
)
|
|
575
556
|
|
|
576
557
|
if len(plain_file_parse_result.required_modules) == 0:
|
|
577
558
|
if len(all_required_modules) > 0:
|
|
@@ -644,7 +625,7 @@ def plain_file_parser( # noqa: C901
|
|
|
644
625
|
# and we need to pass them to the marshalled_plain_source_tree after it's rendered
|
|
645
626
|
plain_source_file_path = Path(plain_source_file_name)
|
|
646
627
|
if plain_source_file_path.suffix != PLAIN_SOURCE_FILE_EXTENSION:
|
|
647
|
-
raise
|
|
628
|
+
raise PlainSyntaxError(
|
|
648
629
|
f"Invalid plain file extension: {plain_source_file_path.suffix}. Expected: {PLAIN_SOURCE_FILE_EXTENSION}."
|
|
649
630
|
)
|
|
650
631
|
|
|
@@ -652,16 +633,13 @@ def plain_file_parser( # noqa: C901
|
|
|
652
633
|
|
|
653
634
|
code_variables = {}
|
|
654
635
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
)
|
|
663
|
-
except PlainModuleNotFound as e:
|
|
664
|
-
raise PlainSyntaxError(e.message)
|
|
636
|
+
plain_file_parse_result = parse_plain_file(
|
|
637
|
+
module_name,
|
|
638
|
+
code_variables,
|
|
639
|
+
template_dirs,
|
|
640
|
+
imported_modules=[],
|
|
641
|
+
modules_trace=[],
|
|
642
|
+
)
|
|
665
643
|
|
|
666
644
|
if len(plain_file_parse_result.required_concepts) > 0:
|
|
667
645
|
missing_required_concepts_msg = "Missing required concepts: "
|
plain_modules.py
CHANGED
|
@@ -7,6 +7,7 @@ from git.exc import NoSuchPathError
|
|
|
7
7
|
|
|
8
8
|
import git_utils
|
|
9
9
|
import plain_spec
|
|
10
|
+
from plain2code_exceptions import ModuleDoesNotExistError
|
|
10
11
|
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
11
12
|
|
|
12
13
|
CODEPLAIN_MEMORY_SUBFOLDER = ".memory"
|
|
@@ -16,10 +17,6 @@ MODULE_FUNCTIONALITIES = "functionalities"
|
|
|
16
17
|
REQUIRED_MODULES_FUNCTIONALITIES = "required_modules_functionalities"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class ModuleDoesNotExistError(Exception):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
|
|
23
20
|
class PlainModule:
|
|
24
21
|
def __init__(self, name: str, build_folder: str):
|
|
25
22
|
self.name = name
|
plain_spec.py
CHANGED
|
@@ -5,9 +5,11 @@ from typing import Optional
|
|
|
5
5
|
|
|
6
6
|
from liquid2.filter import with_context
|
|
7
7
|
|
|
8
|
+
from plain2code_exceptions import InvalidLiquidVariableName
|
|
9
|
+
|
|
8
10
|
DEFINITIONS = "definitions"
|
|
9
|
-
NON_FUNCTIONAL_REQUIREMENTS = "
|
|
10
|
-
TEST_REQUIREMENTS = "test
|
|
11
|
+
NON_FUNCTIONAL_REQUIREMENTS = "implementation reqs"
|
|
12
|
+
TEST_REQUIREMENTS = "test reqs"
|
|
11
13
|
FUNCTIONAL_REQUIREMENTS = "functional specs"
|
|
12
14
|
ACCEPTANCE_TESTS = "acceptance_tests"
|
|
13
15
|
ACCEPTANCE_TEST_HEADING = "acceptance tests"
|
|
@@ -23,10 +25,6 @@ ALLOWED_SPECIFICATION_HEADINGS = [
|
|
|
23
25
|
ALLOWED_IMPORT_SPECIFICATION_HEADINGS = [DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS]
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
class InvalidLiquidVariableName(Exception):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
28
|
def collect_specification_linked_resources(specification, specification_heading, linked_resources_list):
|
|
31
29
|
linked_resources = []
|
|
32
30
|
if "linked_resources" in specification:
|
|
@@ -180,6 +178,26 @@ def get_previous_frid(plain_source_tree, frid):
|
|
|
180
178
|
raise Exception(f"Functional requirement {frid} does not exist.")
|
|
181
179
|
|
|
182
180
|
|
|
181
|
+
def get_frids_before(plain_source_tree, target_frid: str) -> list[str]:
|
|
182
|
+
"""
|
|
183
|
+
Get all FRIDs that appear before the target FRID in the specification.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
plain_source_tree: The plain source tree
|
|
187
|
+
target_frid: The FRID to find predecessors for
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of FRIDs that appear before target_frid, in order
|
|
191
|
+
"""
|
|
192
|
+
previous_frids = []
|
|
193
|
+
for frid in get_frids(plain_source_tree):
|
|
194
|
+
if frid == target_frid:
|
|
195
|
+
break
|
|
196
|
+
previous_frids.append(frid)
|
|
197
|
+
|
|
198
|
+
return previous_frids
|
|
199
|
+
|
|
200
|
+
|
|
183
201
|
def get_specification_item_markdown(specification_item, code_variables, replace_code_variables):
|
|
184
202
|
markdown = specification_item["markdown"]
|
|
185
203
|
if "code_variables" in specification_item:
|
|
@@ -21,6 +21,6 @@ class CreateDist(BaseAction):
|
|
|
21
21
|
render_context.conformance_tests.get_module_conformance_tests_folder(render_context.module_name),
|
|
22
22
|
render_context.conformance_tests_dest,
|
|
23
23
|
)
|
|
24
|
-
console.info(f"Render {render_context.run_state.render_id} completed successfully.")
|
|
24
|
+
console.info(f"[#79FC96]Render {render_context.run_state.render_id} completed successfully.[/#79FC96]")
|
|
25
25
|
|
|
26
26
|
return self.SUCCESSFUL_OUTCOME, None
|
|
@@ -18,7 +18,7 @@ class ExitWithError(BaseAction):
|
|
|
18
18
|
|
|
19
19
|
if render_context.frid_context is not None:
|
|
20
20
|
console.info(
|
|
21
|
-
f"To continue rendering from the last successfully rendered functional requirement, provide the [red]
|
|
21
|
+
f"To continue rendering from the last successfully rendered functional requirement, provide the [red]--render-from {render_context.frid_context.frid}[/red] flag."
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
if render_context.run_state.render_id is not None:
|
|
@@ -14,7 +14,7 @@ class PrepareTestingEnvironment(BaseAction):
|
|
|
14
14
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
15
15
|
if render_context.verbose:
|
|
16
16
|
console.info(
|
|
17
|
-
f"
|
|
17
|
+
f"Running testing environment preparation script {render_context.prepare_environment_script} for build folder {render_context.build_folder}."
|
|
18
18
|
)
|
|
19
19
|
exit_code, _, preparation_temp_file_path = render_utils.execute_script(
|
|
20
20
|
render_context.prepare_environment_script,
|
|
@@ -24,7 +24,7 @@ class RenderConformanceTests(BaseAction):
|
|
|
24
24
|
|
|
25
25
|
def _render_conformance_tests(self, render_context: RenderContext):
|
|
26
26
|
if render_context.verbose:
|
|
27
|
-
console.info("
|
|
27
|
+
console.info("Implementing test requirements:")
|
|
28
28
|
console.print_list(
|
|
29
29
|
render_context.conformance_tests_running_context.current_testing_frid_specifications[
|
|
30
30
|
plain_spec.TEST_REQUIREMENTS
|
|
@@ -149,9 +149,7 @@ class RenderConformanceTests(BaseAction):
|
|
|
149
149
|
]
|
|
150
150
|
|
|
151
151
|
if render_context.verbose:
|
|
152
|
-
console.info("
|
|
153
|
-
console.info(f"[b]{acceptance_test}[/b]")
|
|
154
|
-
console.info()
|
|
152
|
+
console.info(f"Generating acceptance test:\n {acceptance_test}")
|
|
155
153
|
|
|
156
154
|
with console.status(
|
|
157
155
|
f"[{console.INFO_STYLE}]Generating acceptance test for functional requirement {render_context.frid_context.frid}...\n"
|
|
@@ -25,14 +25,14 @@ class RenderFunctionalRequirement(BaseAction):
|
|
|
25
25
|
_, memory_files_content = MemoryManager.fetch_memory_files(render_context.memory_manager.memory_folder)
|
|
26
26
|
|
|
27
27
|
if render_context.verbose:
|
|
28
|
-
msg =
|
|
29
|
-
msg += f"
|
|
28
|
+
msg = "-------------------------------------\n"
|
|
29
|
+
msg += f"Module: {render_context.module_name}\n"
|
|
30
|
+
msg += f"Rendering functionality {render_context.frid_context.frid}"
|
|
30
31
|
if render_context.frid_context.functional_requirement_render_attempts > 1:
|
|
31
32
|
msg += f", attempt number {render_context.frid_context.functional_requirement_render_attempts}/{MAX_CODE_GENERATION_RETRIES}."
|
|
32
|
-
msg += f"\n
|
|
33
|
-
|
|
33
|
+
msg += f"\n{render_context.frid_context.functional_requirement_text}\n"
|
|
34
|
+
msg += "-------------------------------------"
|
|
34
35
|
console.info(msg)
|
|
35
|
-
console.info("-------------------------------------")
|
|
36
36
|
|
|
37
37
|
try:
|
|
38
38
|
if render_context.verbose:
|
|
@@ -54,7 +54,7 @@ class RenderFunctionalRequirement(BaseAction):
|
|
|
54
54
|
render_context.run_state,
|
|
55
55
|
)
|
|
56
56
|
except FunctionalRequirementTooComplex as e:
|
|
57
|
-
error_message = f"The functional requirement:\n
|
|
57
|
+
error_message = f"The functional requirement:\n{render_context.frid_context.functional_requirement_text}\n is too complex to be implemented. Please break down the functional requirement into smaller parts ({str(e)})."
|
|
58
58
|
if e.proposed_breakdown:
|
|
59
59
|
error_message += "\nProposed breakdown:"
|
|
60
60
|
for _, part in e.proposed_breakdown["functional_requirements"].items():
|
|
@@ -32,17 +32,18 @@ class RunConformanceTests(BaseAction):
|
|
|
32
32
|
|
|
33
33
|
if render_context.verbose:
|
|
34
34
|
console.info(
|
|
35
|
-
f"
|
|
35
|
+
f"Running conformance tests script {render_context.conformance_tests_script} "
|
|
36
36
|
+ f"for {conformance_tests_folder_name} ("
|
|
37
37
|
+ f"functional requirement {render_context.conformance_tests_running_context.current_testing_frid} "
|
|
38
38
|
+ f"in module {render_context.conformance_tests_running_context.current_testing_module_name}"
|
|
39
|
-
+ ").
|
|
39
|
+
+ ")."
|
|
40
40
|
)
|
|
41
41
|
exit_code, conformance_tests_issue, conformance_tests_temp_file_path = render_utils.execute_script(
|
|
42
42
|
render_context.conformance_tests_script,
|
|
43
43
|
[render_context.build_folder, conformance_tests_folder_name],
|
|
44
44
|
render_context.verbose,
|
|
45
45
|
"Conformance Tests",
|
|
46
|
+
render_context.conformance_tests_running_context.current_testing_frid,
|
|
46
47
|
)
|
|
47
48
|
render_context.script_execution_history.latest_conformance_test_output_path = conformance_tests_temp_file_path
|
|
48
49
|
render_context.script_execution_history.should_update_script_outputs = True
|
|
@@ -17,7 +17,7 @@ class RunUnitTests(BaseAction):
|
|
|
17
17
|
def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
|
|
18
18
|
if render_context.verbose:
|
|
19
19
|
console.info(
|
|
20
|
-
f"
|
|
20
|
+
f"Running unit tests script {render_context.unittests_script}. (attempt: {render_context.unit_tests_running_context.fix_attempts + 1})"
|
|
21
21
|
)
|
|
22
22
|
exit_code, unittests_issue, unittests_temp_file_path = render_utils.execute_script(
|
|
23
23
|
render_context.unittests_script,
|