codeplain 0.2.2__py3-none-any.whl → 0.2.4__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.2.dist-info → codeplain-0.2.4.dist-info}/METADATA +15 -16
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/RECORD +24 -13
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/WHEEL +1 -2
- codeplain_REST_api.py +0 -6
- diff_utils.py +32 -0
- docs/generate_cli.py +20 -0
- examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +17 -0
- git_utils.py +1 -6
- memory_management.py +97 -0
- plain2code.py +5 -12
- plain2code_exceptions.py +16 -6
- plain_file.py +14 -36
- plain_modules.py +1 -4
- plain_spec.py +2 -4
- tests/__init__.py +1 -0
- tests/conftest.py +34 -0
- tests/test_git_utils.py +458 -0
- tests/test_imports.py +42 -0
- tests/test_plainfile.py +271 -0
- tests/test_plainfileparser.py +532 -0
- tests/test_plainspec.py +67 -0
- tests/test_requires.py +28 -0
- codeplain-0.2.2.dist-info/top_level.txt +0 -41
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/entry_points.txt +0 -0
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeplain
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Transform plain language specifications into working code
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Classifier: Environment :: Console
|
|
6
7
|
Classifier: Intended Audience :: Developers
|
|
7
8
|
Classifier: Operating System :: OS Independent
|
|
8
9
|
Classifier: Topic :: Software Development :: Code Generators
|
|
9
10
|
Requires-Python: >=3.11
|
|
10
|
-
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: python-liquid2==0.3.0
|
|
11
|
+
Requires-Dist: gitpython==3.1.42
|
|
13
12
|
Requires-Dist: mistletoe==1.3.0
|
|
13
|
+
Requires-Dist: networkx==3.6.1
|
|
14
|
+
Requires-Dist: python-frontmatter==1.1.0
|
|
15
|
+
Requires-Dist: python-liquid2==0.3.0
|
|
16
|
+
Requires-Dist: pyyaml==6.0.2
|
|
14
17
|
Requires-Dist: requests==2.32.3
|
|
18
|
+
Requires-Dist: rich==14.2.0
|
|
19
|
+
Requires-Dist: textual==1.0.0
|
|
15
20
|
Requires-Dist: tiktoken==0.12.0
|
|
16
|
-
Requires-Dist: PyYAML==6.0.2
|
|
17
|
-
Requires-Dist: gitpython==3.1.42
|
|
18
21
|
Requires-Dist: transitions==0.9.3
|
|
19
|
-
Requires-Dist: textual==1.0.0
|
|
20
|
-
Requires-Dist: rich==14.2.0
|
|
21
|
-
Requires-Dist: python-frontmatter==1.1.0
|
|
22
|
-
Requires-Dist: networkx==3.6.1
|
|
23
22
|
Provides-Extra: dev
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist: flake8==7.0.0; extra ==
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
|
|
23
|
+
Requires-Dist: black==24.2.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: flake8==7.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: isort==5.13.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy==1.11.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest==8.3.5; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
30
29
|
|
|
31
30
|
# Codeplain plain2code renderer
|
|
32
31
|
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
codeplain_REST_api.py,sha256=
|
|
1
|
+
codeplain_REST_api.py,sha256=NJVOwlsnDFhEySeEds4q2GUKdexTfYGp7FIW0yJqL-A,18026
|
|
2
2
|
concept_utils.py,sha256=vwSY4-FmxyqaDBxhntYzqG8t9pXvAWwiULP0DYQI32o,7905
|
|
3
|
+
diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
|
|
3
4
|
event_bus.py,sha256=sduIR3bgIbxAbLhwKd8Gx9YN9gzaeqy9-mNupS04aKw,1759
|
|
4
5
|
file_utils.py,sha256=4BIxzsteZQOaK-efkvQcoaIfYydsQNFR6elpsxJgXLQ,11591
|
|
5
|
-
git_utils.py,sha256=
|
|
6
|
+
git_utils.py,sha256=7JFHQRzxsO7GoVzP1BO7px2cgrlqyFequbOozHDV8ig,12353
|
|
6
7
|
hash_key.py,sha256=lMKgKpOhPeC4UpFf9e7eCNK20FEaDsXTCJ8j3eCWidE,837
|
|
8
|
+
memory_management.py,sha256=ABrM6PhOsalP_PBq2hCazQvQX9C5rpPHhgT35l68RM4,4357
|
|
7
9
|
module_renderer.py,sha256=LLjLb34phSJlF2YblwGDy5HKWVdi9mo57rr7s-Hj3RA,6261
|
|
8
|
-
plain2code.py,sha256=
|
|
10
|
+
plain2code.py,sha256=vErZtqioqiIN3U3I9U2jUeL7ti_Z38RPcGT0Wuz0xeE,9632
|
|
9
11
|
plain2code_arguments.py,sha256=PdQjd3orklrtlEyz7-Qdz_m2SvjZtZp6dmbkqXgglSU,11826
|
|
10
12
|
plain2code_console.py,sha256=-QorOFimS1AWYK7dmFqI3uhjc51cPtEWsaK4DhFibIw,4139
|
|
11
13
|
plain2code_events.py,sha256=5fPhpRzgJ02xWh0MXqrMv9A4curr2UOE68Y1uKlAcqQ,1347
|
|
12
|
-
plain2code_exceptions.py,sha256=
|
|
14
|
+
plain2code_exceptions.py,sha256=eFvJvFeClFR1MY68zHwPKbNy89ya4iaYSaFRE2GWkTE,929
|
|
13
15
|
plain2code_logger.py,sha256=xSDGN8QAWpA4J6YKyj-kfNWSO-jBl0sfUCFRs7Tflf0,4067
|
|
14
16
|
plain2code_nodes.py,sha256=Om624mfb5MB1Z09c0Zqtb9G1gGkUY9x-v2hzAf3A2gw,3818
|
|
15
17
|
plain2code_read_config.py,sha256=QnpbW_Bi9LzDIOubM5x7fVXH1HgiFL3-5Oa_O1EUoE4,2449
|
|
16
18
|
plain2code_state.py,sha256=InLXdytMXnm9YC4o8MSTbtB1TmqYYUs6rASGBlyA_YY,1274
|
|
17
19
|
plain2code_utils.py,sha256=3xPXWci5yVdpdK_WrNcbinSV94XSeMQDW5UzDuBMaUM,1767
|
|
18
|
-
plain_file.py,sha256=
|
|
19
|
-
plain_modules.py,sha256=
|
|
20
|
-
plain_spec.py,sha256=
|
|
20
|
+
plain_file.py,sha256=lF0ZwK_Hycc4rdaHPxJSfe7mfkRYo5QgMngv50AsF74,28430
|
|
21
|
+
plain_modules.py,sha256=6gteJeiK-WvUZDrIt_kJK0MFiptq63rEVpYvV5yYFXc,4881
|
|
22
|
+
plain_spec.py,sha256=jU4dMVoWumkTK-IBdgXsH4-EnAdyRWxR9dI-lHreEyM,13535
|
|
21
23
|
spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
|
|
22
24
|
system_config.py,sha256=mgHLn-CRHLO9Y9vKyI_eFBreY_YhFad-ctZgBYp-rIg,1777
|
|
23
|
-
codeplain-0.2.2.dist-info/licenses/LICENSE,sha256=wsFi5dpbJurnRNfBj8q2RCcF3ryrmdRIfxc3lPcmc4c,1069
|
|
24
25
|
config/__init__.py,sha256=beYSsJWmBNHDP5rYmVDouqgEeP3t1lkkepbXJ-oq0F8,37
|
|
25
26
|
config/system_config.yaml,sha256=bB5Th5jxgXFyaIvceUPID1ReebMMXsyMibV4gtu9sWQ,1148
|
|
27
|
+
docs/generate_cli.py,sha256=xJmiihtdgqEDBYt_wBsRbniZvIfq7Q6tjAsi3SjoMJg,531
|
|
28
|
+
examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py,sha256=dwTowrHiVKKbrDv21v8xJC30Q57AXZkQasdGOO5JsBE,470
|
|
26
29
|
render_machine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
30
|
render_machine/code_renderer.py,sha256=jOoYivkZzCuGG947YlNPlvD1Dpd2xTWVK8pwfF_OqDU,2957
|
|
28
31
|
render_machine/conformance_test_helpers.py,sha256=6Ru7Dh24SLNIKWfz_sP5hE_EmWpvk6nfgN1T1yaCZus,2887
|
|
@@ -56,6 +59,14 @@ standard_template_library/golang-console-app-template.plain,sha256=M3gE1wZ6o_F-H
|
|
|
56
59
|
standard_template_library/python-console-app-template.plain,sha256=ofIJIkNXScGFTx2w8mY3lP4KmRml2GQv2EiEoXlbqqs,1005
|
|
57
60
|
standard_template_library/typescript-react-app-boilerplate.plain,sha256=6LFxhEOzWnDHdVpFj-XBTq6s4_2dy7LKV3lVkCzv8i4,114
|
|
58
61
|
standard_template_library/typescript-react-app-template.plain,sha256=SG1cBMVt1ncMRK4cOGOyVVj0MmsQTmF4MI67IBn1lFg,380
|
|
62
|
+
tests/__init__.py,sha256=Wk73Io62J15BtlLVIzxmASDWaaJkQLevS4BLK5LDAQg,16
|
|
63
|
+
tests/conftest.py,sha256=QZcp08htUlJGgmHDlRWFgsWXZ8o8IBWTD5QqJaUMlU8,790
|
|
64
|
+
tests/test_git_utils.py,sha256=Knc4R2ZkHYVj9iPFKhb0_ewaad_mxPrakOn6gpziHMc,13138
|
|
65
|
+
tests/test_imports.py,sha256=cdw7u93xNFD064sjwwEWy4oLHhJNlt1-DRDYhHjymFw,2078
|
|
66
|
+
tests/test_plainfile.py,sha256=gUw58XP3PUCDrZxXkrIUNPszlnqY4yW2io3xg6F3ySQ,11086
|
|
67
|
+
tests/test_plainfileparser.py,sha256=9B9r2jEeUKDMhqb4TJEPK7aatYl1op1z92GDAD4fJ20,18685
|
|
68
|
+
tests/test_plainspec.py,sha256=ouS899oejStu8u1D3399y5NAxcKlNbLcinNoxZUp59M,2437
|
|
69
|
+
tests/test_requires.py,sha256=BwlZeLXIs4m-FGnjLjO4vHkxfEX_QgjYoomb3oIu7Vs,1236
|
|
59
70
|
tui/__init__.py,sha256=_fy7FowY0RkWdn4kC7XV2jgvQPCj-2gE8fNUb-Mny0A,60
|
|
60
71
|
tui/components.py,sha256=l7f-jLk0sj9WM8z2IpaHksmSyytXe_bl2GBdHASoIg4,13901
|
|
61
72
|
tui/models.py,sha256=KMjlWubmPD41GA-BjgiqkaV9v7tLJ0fuWKBDPmZj4JA,1493
|
|
@@ -63,8 +74,8 @@ tui/plain2code_tui.py,sha256=7agVU0NnaCuF1LuOSEmjx-XfEOQ6HHr1HlWe_b1Tses,11695
|
|
|
63
74
|
tui/state_handlers.py,sha256=HbjgaV-9xGhp3E-3X114zOqPkeNcCjT-R1PbVRxVdso,12674
|
|
64
75
|
tui/styles.css,sha256=Umm2TLePmywizZGV4Nd8UezZRiK5pFyibYRbpRvGqbs,3056
|
|
65
76
|
tui/widget_helpers.py,sha256=VJorEM2PjRBzN-jIDmKJPolFgo2d8-2NmTumgC5xeNo,5229
|
|
66
|
-
codeplain-0.2.
|
|
67
|
-
codeplain-0.2.
|
|
68
|
-
codeplain-0.2.
|
|
69
|
-
codeplain-0.2.
|
|
70
|
-
codeplain-0.2.
|
|
77
|
+
codeplain-0.2.4.dist-info/METADATA,sha256=HTXpXKt_9V_qDoBoOuuGi-bDsWQ-nGQeItUOOWCaSxQ,4278
|
|
78
|
+
codeplain-0.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
79
|
+
codeplain-0.2.4.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
|
|
80
|
+
codeplain-0.2.4.dist-info/licenses/LICENSE,sha256=wsFi5dpbJurnRNfBj8q2RCcF3ryrmdRIfxc3lPcmc4c,1069
|
|
81
|
+
codeplain-0.2.4.dist-info/RECORD,,
|
codeplain_REST_api.py
CHANGED
|
@@ -65,12 +65,6 @@ class CodeplainAPI:
|
|
|
65
65
|
if response_json["error_code"] == "PlainSyntaxError":
|
|
66
66
|
raise plain2code_exceptions.PlainSyntaxError(response_json["message"])
|
|
67
67
|
|
|
68
|
-
if response_json["error_code"] == "OnlyRelativeLinksAllowed":
|
|
69
|
-
raise plain2code_exceptions.OnlyRelativeLinksAllowed(response_json["message"])
|
|
70
|
-
|
|
71
|
-
if response_json["error_code"] == "LinkMustHaveTextSpecified":
|
|
72
|
-
raise plain2code_exceptions.LinkMustHaveTextSpecified(response_json["message"])
|
|
73
|
-
|
|
74
68
|
if response_json["error_code"] == "NoRenderFound":
|
|
75
69
|
raise plain2code_exceptions.NoRenderFound(response_json["message"])
|
|
76
70
|
|
diff_utils.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from difflib import unified_diff
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_unified_diff(filename: str, existing_file_content: str, response_file_content: str) -> str:
|
|
5
|
+
diff = unified_diff(
|
|
6
|
+
existing_file_content.splitlines(keepends=True),
|
|
7
|
+
response_file_content.splitlines(keepends=True),
|
|
8
|
+
fromfile=filename,
|
|
9
|
+
tofile=filename,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
return "".join(diff)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_code_diff(response_files: dict[str, str], existing_files_content: dict[str, str]) -> dict[str, str]:
|
|
16
|
+
code_diff: dict[str, str] = {}
|
|
17
|
+
for file_name in response_files:
|
|
18
|
+
if file_name in existing_files_content and existing_files_content[file_name]:
|
|
19
|
+
if response_files[file_name]:
|
|
20
|
+
unified_diff_result = get_unified_diff(
|
|
21
|
+
file_name,
|
|
22
|
+
existing_files_content[file_name],
|
|
23
|
+
response_files[file_name],
|
|
24
|
+
)
|
|
25
|
+
if unified_diff_result and unified_diff_result.strip():
|
|
26
|
+
code_diff[file_name] = unified_diff_result
|
|
27
|
+
else:
|
|
28
|
+
code_diff[file_name] = f"File {file_name} was deleted."
|
|
29
|
+
else:
|
|
30
|
+
code_diff[file_name] = response_files[file_name]
|
|
31
|
+
|
|
32
|
+
return code_diff
|
docs/generate_cli.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from plain2code_arguments import create_parser
|
|
5
|
+
|
|
6
|
+
# Add the parent directory to the path so we can import plain2code_arguments
|
|
7
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Get the parser and generate help text
|
|
11
|
+
parser = create_parser()
|
|
12
|
+
help_text = parser.format_help()
|
|
13
|
+
|
|
14
|
+
# Create markdown
|
|
15
|
+
md = "# Plain2Code CLI Reference\n\n```text\n" + help_text + "\n```"
|
|
16
|
+
|
|
17
|
+
# Run generate_cli.py in the docs folder
|
|
18
|
+
|
|
19
|
+
with open("plain2code_cli.md", "w", encoding="utf-8") as f:
|
|
20
|
+
f.write(md)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestHelloWorld(unittest.TestCase):
|
|
8
|
+
def test_hello_world_output(self):
|
|
9
|
+
# Run the hello_world.py script and capture its output
|
|
10
|
+
result = subprocess.run(["python3", "hello_world.py"], capture_output=True, text=True)
|
|
11
|
+
|
|
12
|
+
# Check if the output matches the expected string
|
|
13
|
+
self.assertEqual(result.stdout.strip(), "hello, world")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
unittest.main()
|
git_utils.py
CHANGED
|
@@ -4,6 +4,7 @@ from typing import Optional, Union
|
|
|
4
4
|
from git import Repo
|
|
5
5
|
|
|
6
6
|
import file_utils
|
|
7
|
+
from plain2code_exceptions import InvalidGitRepositoryError
|
|
7
8
|
|
|
8
9
|
FUNCTIONAL_REQUIREMENT_IMPLEMENTED_COMMIT_MESSAGE = (
|
|
9
10
|
"[Codeplain] Implemented code and unit tests for functional requirement {}"
|
|
@@ -25,12 +26,6 @@ MODULE_NAME_MESSAGE = "Module name: {}"
|
|
|
25
26
|
RENDER_ID_MESSAGE = "Render ID: {}"
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
class InvalidGitRepositoryError(Exception):
|
|
29
|
-
"""Raised when the git repository is in an invalid state."""
|
|
30
|
-
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
|
|
34
29
|
def _get_full_commit_message(message, module_name, frid, render_id) -> str:
|
|
35
30
|
full_message = message
|
|
36
31
|
|
memory_management.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import file_utils
|
|
4
|
+
from plain2code_console import console
|
|
5
|
+
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER
|
|
6
|
+
from render_machine.implementation_code_helpers import ImplementationCodeHelpers
|
|
7
|
+
from render_machine.render_context import RenderContext
|
|
8
|
+
|
|
9
|
+
CONFORMANCE_TESTS_SUCCESS_EXIT_CODE = 0
|
|
10
|
+
CONFORMANCE_TEST_MEMORY_SUBFOLDER = "conformance_test_memory"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryManager:
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def fetch_memory_files(memory_folder: str):
|
|
17
|
+
"""Fetch memory files from memory_folder/conformance_test_memory."""
|
|
18
|
+
memory_path = os.path.join(memory_folder, CONFORMANCE_TEST_MEMORY_SUBFOLDER)
|
|
19
|
+
if not os.path.exists(memory_path):
|
|
20
|
+
return {}, {}
|
|
21
|
+
memory_files = file_utils.list_all_text_files(memory_path)
|
|
22
|
+
memory_files_content = file_utils.get_existing_files_content(memory_path, memory_files)
|
|
23
|
+
return memory_files, memory_files_content
|
|
24
|
+
|
|
25
|
+
def __init__(self, codeplain_api, module_build_folder: str):
|
|
26
|
+
self.codeplain_api = codeplain_api
|
|
27
|
+
self.memory_folder = os.path.join(module_build_folder, CODEPLAIN_MEMORY_SUBFOLDER)
|
|
28
|
+
|
|
29
|
+
def create_conformance_tests_memory(
|
|
30
|
+
self, render_context: RenderContext, exit_code: int, conformance_tests_issue: str
|
|
31
|
+
):
|
|
32
|
+
|
|
33
|
+
current_conformance_tests_issue_frid = render_context.conformance_tests_running_context.current_testing_frid
|
|
34
|
+
old_conformance_tests_issue_frid = (
|
|
35
|
+
render_context.conformance_tests_running_context.previous_conformance_tests_issue_frid
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
old_conformance_tests_issue = (
|
|
39
|
+
render_context.conformance_tests_running_context.previous_conformance_tests_issue_old
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
is_first_time_running_conformance_tests = (
|
|
43
|
+
old_conformance_tests_issue_frid is None or old_conformance_tests_issue_frid == ""
|
|
44
|
+
)
|
|
45
|
+
is_same_frid_as_previous_failing_test = current_conformance_tests_issue_frid == old_conformance_tests_issue_frid
|
|
46
|
+
is_conformance_test_failed = exit_code != CONFORMANCE_TESTS_SUCCESS_EXIT_CODE
|
|
47
|
+
|
|
48
|
+
should_create_memory = not is_first_time_running_conformance_tests and (
|
|
49
|
+
is_same_frid_as_previous_failing_test or is_conformance_test_failed
|
|
50
|
+
)
|
|
51
|
+
code_diff_files = render_context.conformance_tests_running_context.code_diff_files
|
|
52
|
+
|
|
53
|
+
if not should_create_memory or code_diff_files is None:
|
|
54
|
+
console.info(
|
|
55
|
+
"Skipping creation of conformance test memory because the conditions for creating memories are not met."
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files(
|
|
60
|
+
render_context.build_folder
|
|
61
|
+
)
|
|
62
|
+
_, memory_files_content = MemoryManager.fetch_memory_files(self.memory_folder)
|
|
63
|
+
|
|
64
|
+
conformance_tests_folder_name = (
|
|
65
|
+
render_context.conformance_tests_running_context.get_current_conformance_test_folder_name()
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
(
|
|
69
|
+
_,
|
|
70
|
+
existing_conformance_test_files_content,
|
|
71
|
+
) = render_context.conformance_tests.fetch_existing_conformance_test_files(
|
|
72
|
+
render_context.module_name,
|
|
73
|
+
render_context.required_modules,
|
|
74
|
+
render_context.conformance_tests_running_context.current_testing_module_name,
|
|
75
|
+
conformance_tests_folder_name,
|
|
76
|
+
)
|
|
77
|
+
acceptance_tests = render_context.conformance_tests_running_context.get_current_acceptance_tests()
|
|
78
|
+
|
|
79
|
+
response_files = render_context.codeplain_api.create_conformance_test_memory(
|
|
80
|
+
render_context.frid_context.frid,
|
|
81
|
+
render_context.plain_source_tree,
|
|
82
|
+
render_context.frid_context.linked_resources,
|
|
83
|
+
existing_files_content,
|
|
84
|
+
memory_files_content,
|
|
85
|
+
render_context.module_name,
|
|
86
|
+
render_context.get_required_modules_functionalities(),
|
|
87
|
+
code_diff_files,
|
|
88
|
+
existing_conformance_test_files_content,
|
|
89
|
+
acceptance_tests,
|
|
90
|
+
conformance_tests_issue,
|
|
91
|
+
conformance_tests_folder_name,
|
|
92
|
+
old_conformance_tests_issue,
|
|
93
|
+
run_state=render_context.run_state,
|
|
94
|
+
)
|
|
95
|
+
if len(response_files) > 0:
|
|
96
|
+
memory_folder_path = os.path.join(self.memory_folder, CONFORMANCE_TEST_MEMORY_SUBFOLDER)
|
|
97
|
+
file_utils.store_response_files(memory_folder_path, response_files, existing_files)
|
plain2code.py
CHANGED
|
@@ -17,7 +17,7 @@ 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 MissingAPIKey, PlainSyntaxError
|
|
20
|
+
from plain2code_exceptions import InvalidFridArgument, MissingAPIKey, PlainSyntaxError
|
|
21
21
|
from plain2code_logger import (
|
|
22
22
|
CrashLogHandler,
|
|
23
23
|
IndentedFormatter,
|
|
@@ -46,10 +46,6 @@ UNRECOVERABLE_ERROR_EXIT_CODES = [69]
|
|
|
46
46
|
TIMEOUT_ERROR_EXIT_CODE = 124
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
class InvalidFridArgument(Exception):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
|
|
53
49
|
def get_render_range(render_range, plain_source):
|
|
54
50
|
render_range = render_range.split(",")
|
|
55
51
|
range_end = render_range[1] if len(render_range) == 2 else render_range[0]
|
|
@@ -227,23 +223,20 @@ def main():
|
|
|
227
223
|
|
|
228
224
|
render(args, run_state, codeplain_api, event_bus)
|
|
229
225
|
except InvalidFridArgument as e:
|
|
230
|
-
console.error(f"
|
|
226
|
+
console.error(f"Invalid FRID argument: {str(e)}.\n")
|
|
231
227
|
# No need to print render ID since this error is going to be thrown at the very start so user will be able to
|
|
232
228
|
# see the render ID that's printed at the very start of the rendering process.
|
|
233
229
|
dump_crash_logs(args)
|
|
234
230
|
except FileNotFoundError as e:
|
|
235
|
-
console.error(f"
|
|
231
|
+
console.error(f"File not found: {str(e)}\n")
|
|
236
232
|
console.debug(f"Render ID: {run_state.render_id}")
|
|
237
233
|
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
234
|
except TemplateNotFoundError as e:
|
|
242
|
-
console.error(f"
|
|
235
|
+
console.error(f"Template not found: {str(e)}\n")
|
|
243
236
|
console.error(system_config.get_error_message("template_not_found"))
|
|
244
237
|
dump_crash_logs(args)
|
|
245
238
|
except PlainSyntaxError as e:
|
|
246
|
-
console.error(f"
|
|
239
|
+
console.error(f"Plain syntax error: {str(e)}\n")
|
|
247
240
|
dump_crash_logs(args)
|
|
248
241
|
except KeyboardInterrupt:
|
|
249
242
|
console.error("Keyboard interrupt")
|
plain2code_exceptions.py
CHANGED
|
@@ -25,25 +25,35 @@ class PlainSyntaxError(Exception):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class NoRenderFound(Exception):
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class
|
|
32
|
+
class MultipleRendersFound(Exception):
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class
|
|
36
|
+
class UnexpectedState(Exception):
|
|
37
37
|
pass
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class
|
|
40
|
+
class MissingAPIKey(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):
|
|
49
59
|
pass
|
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
|
|
|
@@ -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,6 +5,8 @@ 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
11
|
NON_FUNCTIONAL_REQUIREMENTS = "technical specs"
|
|
10
12
|
TEST_REQUIREMENTS = "test specs"
|
|
@@ -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:
|
tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Tests package
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
os.environ["MASTER_KEY"] = "test-master-key"
|
|
6
|
+
os.environ["GOOGLE_API_KEY"] = "test-google-api-key"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def test_data_path():
|
|
11
|
+
"""Returns the path to the test data directory."""
|
|
12
|
+
return os.path.dirname(__file__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def load_test_data(test_data_path):
|
|
17
|
+
"""Returns a function to load test data files."""
|
|
18
|
+
|
|
19
|
+
def _load(file_name):
|
|
20
|
+
file_path = os.path.join(test_data_path, file_name)
|
|
21
|
+
with open(file_path, "r") as file:
|
|
22
|
+
return file.read()
|
|
23
|
+
|
|
24
|
+
return _load
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def get_test_data_path(test_data_path):
|
|
29
|
+
"""Returns a function to get the path to a test data subfolder."""
|
|
30
|
+
|
|
31
|
+
def _get_path(subfolder_name):
|
|
32
|
+
return os.path.join(test_data_path, subfolder_name)
|
|
33
|
+
|
|
34
|
+
return _get_path
|