codeplain 0.3.11.dev18__py3-none-any.whl → 0.3.11.dev19__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeplain
3
- Version: 0.3.11.dev18
3
+ Version: 0.3.11.dev19
4
4
  Summary: Transform plain language specifications into working code
5
5
  License-File: LICENSE
6
6
  Classifier: Environment :: Console
@@ -4,7 +4,7 @@ concept_utils.py,sha256=TpyiQ4DfMtO231j-lPj65i0suRTPr8t9j3QNf9KiM4A,8080
4
4
  conformance_fix_journal.py,sha256=7wL3UOc3BWaUo5MCmiFbb3kIwOQ6iEpdQSyEmedo8Vk,9488
5
5
  diff_utils.py,sha256=AjiQlqo5pRos_8hVXZo5yBurl5BzSrTMGrQv4dCtRCg,1198
6
6
  event_bus.py,sha256=g5XLwRfjrvTIcB6urjREuCJzZj5TA93hy8lKXLGXuCE,650
7
- file_utils.py,sha256=MNZ5li1ofIMOYB6FCrMa_UkX6nkGCgSZuaqzhqVc1Ok,11682
7
+ file_utils.py,sha256=IshKvld9LJLwXSB3lxZ1HQyVHa93I626BsTScBu95ss,12118
8
8
  git_preflight.py,sha256=vB2A8GyYz_iWr8jOE4POFLBHr3qihmedWuidspGdsrw,1509
9
9
  git_utils.py,sha256=gCUA6_9zg19f6k_0aVcRnIVlAJSBD0Vt6takx-0pKME,16547
10
10
  memory_management.py,sha256=CwIOQqF7Xo1sNfvP69zQRK_CpjinQD9dbolhRPl6lTk,2446
@@ -23,7 +23,7 @@ plain2code_read_config.py,sha256=D5CxnmTRjddupecWvstcUm0kIJWWzq5-Zmk6sU0wbIg,223
23
23
  plain2code_state.py,sha256=uePQlC29Pbhm1yXfdCFebONYdy6jo13gTTmXSwC11cQ,3168
24
24
  plain2code_telemetry.py,sha256=e4UCAoFklJMDWGQsL50i7nTLiOBy8gs86A0XO4WLgPo,4597
25
25
  plain2code_utils.py,sha256=unOxuqSQRpwG8YF6uoQhnZ3ZSTJRIUCPLdJ-1lRZR5M,1534
26
- plain_file.py,sha256=5UkjZNePMGmUhGqduwbm6R6DnT_rDX7q3gd8IzIDAF8,37092
26
+ plain_file.py,sha256=yzdHYBZye1UjI6K-vcod5u9hPqQ_zBQS050yypdmB3M,37614
27
27
  plain_modules.py,sha256=nXlmw0J2rKkuU1Y3A68LVtZ1Ffdc-D5iMjxaXSaSHTQ,25963
28
28
  plain_spec.py,sha256=7nb5lLJKHZ3uu9THkSXyJ5brIKkbGW0ooVfRclLdlzA,16062
29
29
  system_config.py,sha256=sA8wiPmBMtRsLwSQ40U5pVdLazAafbV-1Z19Yp0HiSQ,5314
@@ -93,7 +93,7 @@ tests/test_plain2code_state.py,sha256=cowPZS9oRbv9J49CE4P3Rc47Q5oQOby4bTf2OlnRlB
93
93
  tests/test_plain2code_utils.py,sha256=-W8a7FquMJQ_pTdBoBF1XprzNL2G555QhdtXCy6BDyQ,1237
94
94
  tests/test_plain_modules.py,sha256=Fn3FeH-M4exnyFJDZzc9YWaLEfBmiqPH5TpiBT9jgAs,33033
95
95
  tests/test_plainfile.py,sha256=wZ_PN4GULPWIpl_AK_r_6jkgtX0Ch8EHmN5IhX7huOE,11027
96
- tests/test_plainfileparser.py,sha256=M-7VDs0N79yqJOZRv55K12Kaz73oFz7Lqh3L_dqcpX4,25199
96
+ tests/test_plainfileparser.py,sha256=OY7dCilaUEeM4vAOShQYsZXKQzzUOi7nGYBaROu8Te8,26606
97
97
  tests/test_plainspec.py,sha256=ng55E8ajrH2-gPC1p_QDIe_zXplPvoqHJ-d57UWKFTw,3577
98
98
  tests/test_prepare_repositories_layout.py,sha256=PhJQLnHfjfjo6CTWoicyEKgL0_GtjwB2OKYAWSBVHtk,14638
99
99
  tests/test_render_finished_report.py,sha256=QtViWItlo8YB8bLOv3YwvfLprVZgZ9r8Q1Td7_vGi18,7445
@@ -117,8 +117,8 @@ tui/spinner.py,sha256=Ro6Gd9Przf-whuHqPRY6HwI0T57yJjyNPbhDbigZKZE,2471
117
117
  tui/state_handlers.py,sha256=zbUS_D9eU8qIwVpaxPEp9zL9bpg8gXsWNoUaqg9TLrQ,16664
118
118
  tui/styles.css,sha256=kiC7Og_2G1E7owoiZgV95-3_dSeQnsNj3qzaY04xkDg,6727
119
119
  tui/widget_helpers.py,sha256=jitm2WHiKj-NVFL-hULxWzckPreRbUTl9OIZLN31-ek,6997
120
- codeplain-0.3.11.dev18.dist-info/METADATA,sha256=zpMl6ZYhRFiw3yPqVZLyxFmFQkGagK6_kgmA8ELWkuI,7257
121
- codeplain-0.3.11.dev18.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
122
- codeplain-0.3.11.dev18.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
123
- codeplain-0.3.11.dev18.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
124
- codeplain-0.3.11.dev18.dist-info/RECORD,,
120
+ codeplain-0.3.11.dev19.dist-info/METADATA,sha256=hh5z_WBJjGHeYBl89nFPmHXI5Ed51lYqAb_-SutUCX8,7257
121
+ codeplain-0.3.11.dev19.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
122
+ codeplain-0.3.11.dev19.dist-info/entry_points.txt,sha256=oDZkBqu9WhtZApb_K6ia8-fn9aojwmAsgnKELceX5T4,46
123
+ codeplain-0.3.11.dev19.dist-info/licenses/LICENSE,sha256=pCeKgQ1mXE5OmNUuKOfOZh1T1vqkeCUxghE7N8qgnzc,11345
124
+ codeplain-0.3.11.dev19.dist-info/RECORD,,
file_utils.py CHANGED
@@ -180,6 +180,20 @@ def open_from(dirs, file_name):
180
180
  return None
181
181
 
182
182
 
183
+ def resolve_linked_resource(dirs, file_name):
184
+ """Return the first existing path for file_name across dirs, or None if not found.
185
+
186
+ Mirrors the search order used by open_from so that validating a linked resource and
187
+ loading it can never disagree.
188
+ """
189
+ for dir in dirs:
190
+ full_file_name = os.path.join(dir, file_name)
191
+ if os.path.exists(full_file_name):
192
+ return full_file_name
193
+
194
+ return None
195
+
196
+
183
197
  def load_linked_resources(template_dirs: list[str], resources_list, module_name: str):
184
198
  linked_resources = {}
185
199
 
plain_file.py CHANGED
@@ -90,7 +90,7 @@ def remove_quotes(token):
90
90
  token.children = tuple(new_children)
91
91
 
92
92
 
93
- def check_section_for_linked_resources(section):
93
+ def check_section_for_linked_resources(section, template_dirs):
94
94
  linked_resources = []
95
95
  for link in traverse(section, klass=Link):
96
96
  parsed_url = urlparse(link.node.target)
@@ -99,11 +99,19 @@ def check_section_for_linked_resources(section):
99
99
  f"Plain syntax error: Only relative links are allowed (text: {link.node.children[0].content}, target: {link.node.target})."
100
100
  )
101
101
 
102
- if not os.path.exists(link.node.target):
103
- raise PlainSyntaxError(f"Plain syntax error: Link {link.node.target} does not exist.")
102
+ resolved_target = file_utils.resolve_linked_resource(template_dirs, link.node.target)
103
+ if resolved_target is None:
104
+ searched_dirs = "\n".join(f" {position}. {dir}" for position, dir in enumerate(template_dirs, start=1))
105
+ raise PlainSyntaxError(
106
+ f"Plain syntax error: Link {link.node.target} does not exist. "
107
+ f"Linked resources are looked up relative to the following directories "
108
+ f"(highest to lowest precedence):\n{searched_dirs}"
109
+ )
104
110
 
105
- if not os.path.isfile(link.node.target):
106
- raise PlainSyntaxError(f"Plain syntax error: Link {link.node.target} must be a file.")
111
+ if not os.path.isfile(resolved_target):
112
+ raise PlainSyntaxError(
113
+ f"Plain syntax error: Link {link.node.target} must be a file (resolved to {resolved_target})."
114
+ )
107
115
 
108
116
  if len(link.node.children) != 1:
109
117
  raise PlainSyntaxError(f"Plain syntax error: Link must have text specified (link: {link.node.target}).")
@@ -122,15 +130,15 @@ def check_section_for_linked_resources(section):
122
130
  section.linked_resources = linked_resources
123
131
 
124
132
 
125
- def check_for_linked_resources(plain_source):
133
+ def check_for_linked_resources(plain_source, template_dirs):
126
134
  for specification_heading in plain_spec.ALLOWED_SPECIFICATION_HEADINGS:
127
135
  if specification_heading in plain_source and hasattr(plain_source[specification_heading], "children"):
128
136
  for requirement in plain_source[specification_heading].children:
129
- check_section_for_linked_resources(requirement)
137
+ check_section_for_linked_resources(requirement, template_dirs)
130
138
 
131
139
  if hasattr(requirement, plain_spec.ACCEPTANCE_TESTS):
132
140
  for acceptance_test in requirement.acceptance_tests:
133
- check_section_for_linked_resources(acceptance_test)
141
+ check_section_for_linked_resources(acceptance_test, template_dirs)
134
142
 
135
143
 
136
144
  def process_section_code_variables(section, code_variables):
@@ -845,7 +853,7 @@ def plain_file_parser( # noqa: C901
845
853
 
846
854
  process_acceptance_tests(plain_file_parse_result.plain_source)
847
855
 
848
- check_for_linked_resources(plain_file_parse_result.plain_source)
856
+ check_for_linked_resources(plain_file_parse_result.plain_source, template_dirs)
849
857
 
850
858
  marshalled_plain_source = marshall_plain_source(plain_file_parse_result.plain_source)
851
859
 
@@ -146,14 +146,30 @@ def test_invalid_absolute_link(get_test_data_path):
146
146
  )
147
147
 
148
148
 
149
+ def test_missing_link_error_lists_searched_directories(get_test_data_path):
150
+ """The error tells the user which directories were searched for the linked resource."""
151
+ plain_file_dir = get_test_data_path("data/plainfile")
152
+
153
+ with pytest.raises(PlainSyntaxError) as exception_info:
154
+ plain_file.plain_file_parser(
155
+ "plain_source_with_missing_link.plain",
156
+ [plain_file_dir],
157
+ )
158
+
159
+ message = str(exception_info.value)
160
+ assert "resources/missing_resource.yaml does not exist" in message
161
+ assert "highest to lowest precedence" in message
162
+ assert plain_file_dir in message
163
+
164
+
149
165
  def test_reference_link_parsing(get_test_data_path):
150
166
  _, plain_sections, _ = plain_file.plain_file_parser(
151
167
  "task_manager_with_reference_links.plain",
152
168
  [get_test_data_path("data/plainfile")],
153
169
  )
154
170
  asserted_resources = [
155
- "tests/data/plainfile/task_list_ui_specification.yaml",
156
- "tests/data/plainfile/add_new_task_modal_specification.yaml",
171
+ "task_list_ui_specification.yaml",
172
+ "add_new_task_modal_specification.yaml",
157
173
  ]
158
174
  for functional_requirement in plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS]:
159
175
  if "linked_resources" not in functional_requirement:
@@ -180,13 +196,36 @@ def test_reference_link_parsing(get_test_data_path):
180
196
  assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][1]["linked_resources"] == [
181
197
  {
182
198
  "text": "task_list_ui_specification.yaml",
183
- "target": "tests/data/plainfile/task_list_ui_specification.yaml",
199
+ "target": "task_list_ui_specification.yaml",
200
+ }
201
+ ]
202
+ assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][2]["linked_resources"] == [
203
+ {
204
+ "text": "add_new_task_modal_specification.yaml",
205
+ "target": "add_new_task_modal_specification.yaml",
206
+ }
207
+ ]
208
+
209
+
210
+ def test_reference_link_parsing_independent_of_working_directory(get_test_data_path, monkeypatch, tmp_path):
211
+ """Linked resources resolve against the plain file's directory, not the invocation cwd."""
212
+ monkeypatch.chdir(tmp_path)
213
+
214
+ _, plain_sections, _ = plain_file.plain_file_parser(
215
+ "task_manager_with_reference_links.plain",
216
+ [get_test_data_path("data/plainfile")],
217
+ )
218
+
219
+ assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][1]["linked_resources"] == [
220
+ {
221
+ "text": "task_list_ui_specification.yaml",
222
+ "target": "task_list_ui_specification.yaml",
184
223
  }
185
224
  ]
186
225
  assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][2]["linked_resources"] == [
187
226
  {
188
227
  "text": "add_new_task_modal_specification.yaml",
189
- "target": "tests/data/plainfile/add_new_task_modal_specification.yaml",
228
+ "target": "add_new_task_modal_specification.yaml",
190
229
  }
191
230
  ]
192
231