plain-parser 0.1.0__tar.gz → 0.1.1.dev2__tar.gz
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.
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/PKG-INFO +16 -1
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/README.md +14 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/pyproject.toml +4 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/__init__.py +2 -0
- plain_parser-0.1.1.dev2/src/plain_parser/cli.py +117 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/plain_file.py +84 -41
- plain_parser-0.1.1.dev2/tests/data/cli/acceptance_test_binary_resource.plain +10 -0
- plain_parser-0.1.1.dev2/tests/data/cli/acceptance_test_resource.plain +10 -0
- plain_parser-0.1.1.dev2/tests/data/cli/base64_resource.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/cli/binary.bin +0 -0
- plain_parser-0.1.1.dev2/tests/data/cli/binary_resource.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/cli/code_variable_conflict.plain +9 -0
- plain_parser-0.1.1.dev2/tests/data/cli/code_variable_template.plain +1 -0
- plain_parser-0.1.1.dev2/tests/data/cli/missing_resource.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/cli/not_plain.md +1 -0
- plain_parser-0.1.1.dev2/tests/data/cli/notes.md +3 -0
- plain_parser-0.1.1.dev2/tests/data/cli/requires_binary_resource_base.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/cli/requires_binary_resource_top.plain +12 -0
- plain_parser-0.1.1.dev2/tests/data/cli/subdir/nested.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/cli/undefined_concept.plain +13 -0
- plain_parser-0.1.1.dev2/tests/data/cli/valid.plain +11 -0
- plain_parser-0.1.1.dev2/tests/data/requires/chain_base.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/requires/chain_fork_top.plain +13 -0
- plain_parser-0.1.1.dev2/tests/data/requires/chain_middle.plain +12 -0
- plain_parser-0.1.1.dev2/tests/data/requires/chain_top.plain +12 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/diamond_requires_1.plain +4 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/diamond_requires_2.plain +4 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/independent_requires_1.plain +4 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/independent_requires_2.plain +4 -0
- plain_parser-0.1.1.dev2/tests/data/requires/invalid_ancestor_base.plain +7 -0
- plain_parser-0.1.1.dev2/tests/data/requires/invalid_ancestor_top.plain +12 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/normal_requires_1.plain +4 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/normal_requires_2.plain +4 -0
- plain_parser-0.1.1.dev2/tests/test_cli.py +325 -0
- plain_parser-0.1.1.dev2/tests/test_requires.py +60 -0
- plain_parser-0.1.0/tests/test_requires.py +0 -28
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/.flake8 +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/.github/workflows/lint-and-test.yml +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/.github/workflows/publish-dev-to-pypi.yml +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/.github/workflows/publish-to-pypi.yml +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/.gitignore +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/LICENSE +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/concept_utils.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/exceptions.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/liquid_nodes.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/loaders.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/src/plain_parser/plain_spec.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/__init__.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/conftest.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/circular_imports_1.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/circular_imports_2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/circular_imports_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/diamond_import_1.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/diamond_import_2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/diamond_import_common.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/diamond_imports_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/import_with_functionalities.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/imports/non_existent_import.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/invalid_specification_order.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/cyclic_definitions.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_imported_exported.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_malformed.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_missing_definition.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_no_definitions.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_not_declared.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/requires_without_definitions_example.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/topological_sort.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/circular_requires_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/circular_requires_sub.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/diamond_requires_common.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/diamond_requires_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/independent_requires_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/non_existent_require.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/normal_requires_common.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/requires/normal_requires_main.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/sample_base64_image.txt +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/simple.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/block_level_include.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/code_variables.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/header.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/implement.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/implement_2.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/template_include.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/data/templates/test_hardest_problem.plain +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_exceptions.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_imports.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_loaders.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_plainfile.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_plainfileparser.py +0 -0
- {plain_parser-0.1.0 → plain_parser-0.1.1.dev2}/tests/test_plainspec.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: plain-parser
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1.dev2
|
|
4
4
|
Summary: Parser for ***plain specification files
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -13,6 +13,7 @@ Requires-Dist: mistletoe<2,>=1.6.0
|
|
|
13
13
|
Requires-Dist: networkx<4,>=3.6.1
|
|
14
14
|
Requires-Dist: python-frontmatter<2,>=1.3.0
|
|
15
15
|
Requires-Dist: python-liquid2<0.5,>=0.4.0
|
|
16
|
+
Requires-Dist: pyyaml<7,>=6
|
|
16
17
|
Provides-Extra: dev
|
|
17
18
|
Requires-Dist: black==26.5.1; extra == 'dev'
|
|
18
19
|
Requires-Dist: flake8==7.3.0; extra == 'dev'
|
|
@@ -37,6 +38,20 @@ pip install plain-parser
|
|
|
37
38
|
|
|
38
39
|
## Usage
|
|
39
40
|
|
|
41
|
+
Validate a module and its `requires` chain from the command line (`<file>: OK` and exit `0` when valid; `Error: …` on stderr and exit `1` otherwise):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
plain-parser check my_spec.plain [--template-dir DIR] [--config-name NAME]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Modules and `{% include %}` templates are looked up in the `.plain` file's directory, then in `--template-dir`.
|
|
48
|
+
When `--template-dir` is not given, it is read from the `template-dir` key of `config.yaml` (or the file named by
|
|
49
|
+
`--config-name`), found next to the `.plain` file or in the working directory. Finding it in both is a usage error
|
|
50
|
+
(exit `2`). The config file is read even when `--template-dir` is given, so a malformed one fails the run either way.
|
|
51
|
+
Other config keys are ignored.
|
|
52
|
+
|
|
53
|
+
Or from Python:
|
|
54
|
+
|
|
40
55
|
```python
|
|
41
56
|
from plain_parser import plain_file_parser
|
|
42
57
|
|
|
@@ -14,6 +14,20 @@ pip install plain-parser
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
+
Validate a module and its `requires` chain from the command line (`<file>: OK` and exit `0` when valid; `Error: …` on stderr and exit `1` otherwise):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
plain-parser check my_spec.plain [--template-dir DIR] [--config-name NAME]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Modules and `{% include %}` templates are looked up in the `.plain` file's directory, then in `--template-dir`.
|
|
24
|
+
When `--template-dir` is not given, it is read from the `template-dir` key of `config.yaml` (or the file named by
|
|
25
|
+
`--config-name`), found next to the `.plain` file or in the working directory. Finding it in both is a usage error
|
|
26
|
+
(exit `2`). The config file is read even when `--template-dir` is given, so a malformed one fails the run either way.
|
|
27
|
+
Other config keys are ignored.
|
|
28
|
+
|
|
29
|
+
Or from Python:
|
|
30
|
+
|
|
17
31
|
```python
|
|
18
32
|
from plain_parser import plain_file_parser
|
|
19
33
|
|
|
@@ -21,6 +21,7 @@ dependencies = [
|
|
|
21
21
|
"mistletoe>=1.6.0,<2",
|
|
22
22
|
"python-frontmatter>=1.3.0,<2",
|
|
23
23
|
"networkx>=3.6.1,<4",
|
|
24
|
+
"pyyaml>=6,<7",
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
[project.optional-dependencies]
|
|
@@ -32,6 +33,9 @@ dev = [
|
|
|
32
33
|
"mypy==2.3.0",
|
|
33
34
|
]
|
|
34
35
|
|
|
36
|
+
[project.scripts]
|
|
37
|
+
plain-parser = "plain_parser.cli:main"
|
|
38
|
+
|
|
35
39
|
[tool.hatch.version]
|
|
36
40
|
source = "vcs"
|
|
37
41
|
|
|
@@ -6,6 +6,7 @@ from plain_parser.plain_file import (
|
|
|
6
6
|
get_filename_from_module_name,
|
|
7
7
|
get_module_name_from_filename,
|
|
8
8
|
marshall_plain_source,
|
|
9
|
+
parse_module_chain,
|
|
9
10
|
parse_plain_file,
|
|
10
11
|
parse_plain_source,
|
|
11
12
|
plain_file_parser,
|
|
@@ -19,6 +20,7 @@ __all__ = [
|
|
|
19
20
|
"get_module_name_from_filename",
|
|
20
21
|
"loaders",
|
|
21
22
|
"marshall_plain_source",
|
|
23
|
+
"parse_module_chain",
|
|
22
24
|
"parse_plain_file",
|
|
23
25
|
"parse_plain_source",
|
|
24
26
|
"plain_file",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Command-line entry point: ``plain-parser check <file.plain> [--template-dir DIR] [--config-name NAME]``."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from plain_parser import loaders, plain_file, plain_spec
|
|
10
|
+
|
|
11
|
+
DEFAULT_CONFIG_NAME = "config.yaml"
|
|
12
|
+
TEMPLATE_DIR_CONFIG_KEYS = ("template_dir", "template-dir")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AmbiguousConfigFileError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
20
|
+
parser = argparse.ArgumentParser(prog="plain-parser", description="Tools for ***plain specification files.")
|
|
21
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
22
|
+
|
|
23
|
+
check_parser = subparsers.add_parser("check", help="Validate a .plain module and its requires chain.")
|
|
24
|
+
check_parser.add_argument("plain_file", help="Path to the .plain module.")
|
|
25
|
+
check_parser.add_argument(
|
|
26
|
+
"--template-dir",
|
|
27
|
+
help="Directory searched for modules and templates after the .plain file's own directory. "
|
|
28
|
+
"Overrides the template-dir value of the config file.",
|
|
29
|
+
)
|
|
30
|
+
check_parser.add_argument(
|
|
31
|
+
"--config-name",
|
|
32
|
+
default=DEFAULT_CONFIG_NAME,
|
|
33
|
+
help="Name of the config file to read template-dir from. Looked up in the .plain file's directory "
|
|
34
|
+
"and the current working directory. Defaults to %(default)s.",
|
|
35
|
+
)
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_config_file(config_name: str, plain_file_path: str) -> str | None:
|
|
40
|
+
"""The config file next to the .plain file, else the one in the working directory, else None."""
|
|
41
|
+
plain_file_dir = os.path.dirname(os.path.abspath(plain_file_path))
|
|
42
|
+
plain_dir_config = os.path.normpath(os.path.join(plain_file_dir, config_name))
|
|
43
|
+
cwd_config = os.path.normpath(os.path.join(os.getcwd(), config_name))
|
|
44
|
+
|
|
45
|
+
in_plain_dir = os.path.exists(plain_dir_config)
|
|
46
|
+
in_cwd = os.path.exists(cwd_config)
|
|
47
|
+
if in_plain_dir and in_cwd and plain_dir_config != cwd_config:
|
|
48
|
+
raise AmbiguousConfigFileError(
|
|
49
|
+
f"Config file '{config_name}' was found in two locations:\n"
|
|
50
|
+
f" - Plain file directory: {plain_file_dir}\n"
|
|
51
|
+
f" - Current working directory: {os.getcwd()}\n"
|
|
52
|
+
f"Remove the config file from one of these locations to resolve the ambiguity."
|
|
53
|
+
)
|
|
54
|
+
if in_plain_dir:
|
|
55
|
+
return plain_dir_config
|
|
56
|
+
if in_cwd:
|
|
57
|
+
return cwd_config
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def template_dir_from_config(config_file: str) -> str | None:
|
|
62
|
+
"""The template-dir value of the config file, resolved against the file's directory. Other keys are ignored."""
|
|
63
|
+
with open(config_file) as f:
|
|
64
|
+
config = yaml.safe_load(f) or {}
|
|
65
|
+
|
|
66
|
+
for key in TEMPLATE_DIR_CONFIG_KEYS:
|
|
67
|
+
value = config.get(key)
|
|
68
|
+
if value:
|
|
69
|
+
value = os.path.expanduser(str(value))
|
|
70
|
+
return value if os.path.isabs(value) else os.path.join(os.path.dirname(config_file), value)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def check(plain_file_path: str, template_dir: str | None) -> None:
|
|
75
|
+
"""Raise if the module, its requires chain, or its linked resources are invalid."""
|
|
76
|
+
template_dirs = [os.path.dirname(os.path.abspath(plain_file_path))]
|
|
77
|
+
if template_dir:
|
|
78
|
+
template_dirs.append(template_dir)
|
|
79
|
+
|
|
80
|
+
chain = plain_file.parse_module_chain(os.path.basename(plain_file_path), template_dirs)
|
|
81
|
+
|
|
82
|
+
for module_name, plain_source_tree in chain:
|
|
83
|
+
resources_list: list[dict] = []
|
|
84
|
+
plain_spec.collect_linked_resources(plain_source_tree, resources_list, None, True)
|
|
85
|
+
loaders.load_linked_resources(template_dirs, resources_list, module_name)
|
|
86
|
+
|
|
87
|
+
_, top_plain_source_tree = chain[-1]
|
|
88
|
+
for frid in plain_spec.get_frids(top_plain_source_tree):
|
|
89
|
+
plain_spec.get_specifications_for_frid(top_plain_source_tree, frid)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main(argv: list[str] | None = None) -> int:
|
|
93
|
+
parser = build_parser()
|
|
94
|
+
args = parser.parse_args(argv)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# The config file is resolved even when --template-dir is given, so an ambiguous config is always an error.
|
|
98
|
+
config_template_dir = _template_dir_from_config(parser, args)
|
|
99
|
+
check(args.plain_file, args.template_dir or config_template_dir)
|
|
100
|
+
except KeyboardInterrupt:
|
|
101
|
+
return 130
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
104
|
+
return 1
|
|
105
|
+
|
|
106
|
+
print(f"{args.plain_file}: OK")
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _template_dir_from_config(parser: argparse.ArgumentParser, args: argparse.Namespace) -> str | None:
|
|
111
|
+
try:
|
|
112
|
+
config_file = resolve_config_file(args.config_name, args.plain_file)
|
|
113
|
+
return template_dir_from_config(config_file) if config_file is not None else None
|
|
114
|
+
except AmbiguousConfigFileError as e:
|
|
115
|
+
parser.error(str(e))
|
|
116
|
+
except Exception as e:
|
|
117
|
+
parser.error(f"Error reading config file: {e}")
|
|
@@ -701,11 +701,16 @@ def parse_plain_file(
|
|
|
701
701
|
|
|
702
702
|
def process_required_modules(
|
|
703
703
|
required_modules: list[str],
|
|
704
|
-
code_variables: dict,
|
|
705
704
|
template_dirs: list[str],
|
|
706
705
|
all_required_modules: list[str],
|
|
707
706
|
modules_trace: list[str],
|
|
707
|
+
chain: list[tuple[str, dict]],
|
|
708
708
|
) -> list[mistletoe.block_token.token]:
|
|
709
|
+
"""Parse and fully validate every required module, deepest first.
|
|
710
|
+
|
|
711
|
+
Appends ``(module name, marshalled plain source tree)`` to ``chain`` for each module not already in it
|
|
712
|
+
and returns the exported definitions of the directly required modules.
|
|
713
|
+
"""
|
|
709
714
|
exported_definitions = list[mistletoe.block_token.token]()
|
|
710
715
|
for module_name in required_modules:
|
|
711
716
|
if module_name in modules_trace:
|
|
@@ -714,10 +719,12 @@ def process_required_modules(
|
|
|
714
719
|
if len(all_required_modules) > 0 and module_name == all_required_modules[-1]:
|
|
715
720
|
continue
|
|
716
721
|
|
|
722
|
+
code_variables: dict = {}
|
|
717
723
|
plain_file_parse_result = parse_plain_file(
|
|
718
724
|
module_name, code_variables, template_dirs, imported_modules=[], modules_trace=[]
|
|
719
725
|
)
|
|
720
726
|
|
|
727
|
+
ancestor_exported_definitions: list[mistletoe.block_token.token] = []
|
|
721
728
|
if len(plain_file_parse_result.required_modules) == 0:
|
|
722
729
|
if len(all_required_modules) > 0:
|
|
723
730
|
# For now we require that there is fixed order how required modules are dependent.
|
|
@@ -727,12 +734,12 @@ def process_required_modules(
|
|
|
727
734
|
f"Plain syntax error: There must be a fixed order how required modules are dependent ({module_name})."
|
|
728
735
|
)
|
|
729
736
|
else:
|
|
730
|
-
process_required_modules(
|
|
737
|
+
ancestor_exported_definitions = process_required_modules(
|
|
731
738
|
plain_file_parse_result.required_modules,
|
|
732
|
-
code_variables,
|
|
733
739
|
template_dirs,
|
|
734
740
|
all_required_modules,
|
|
735
741
|
modules_trace + [module_name],
|
|
742
|
+
chain,
|
|
736
743
|
)
|
|
737
744
|
|
|
738
745
|
if EXPORTED_CONCEPTS_DIRECTIVE in plain_file_parse_result.plain_source_obj.metadata:
|
|
@@ -748,6 +755,12 @@ def process_required_modules(
|
|
|
748
755
|
)
|
|
749
756
|
)
|
|
750
757
|
|
|
758
|
+
marshalled_plain_source = validate_and_marshall_module(
|
|
759
|
+
plain_file_parse_result, module_name, ancestor_exported_definitions, code_variables
|
|
760
|
+
)
|
|
761
|
+
if module_name not in (chain_module_name for chain_module_name, _ in chain):
|
|
762
|
+
chain.append((module_name, marshalled_plain_source))
|
|
763
|
+
|
|
751
764
|
all_required_modules.append(module_name)
|
|
752
765
|
|
|
753
766
|
return exported_definitions
|
|
@@ -775,35 +788,13 @@ def process_exported_definitions(plain_source: dict, exported_definitions: list[
|
|
|
775
788
|
plain_source[plain_spec.DEFINITIONS].children.append(exported_definition)
|
|
776
789
|
|
|
777
790
|
|
|
778
|
-
def
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
plain_source_file_path = Path(plain_source_file_name)
|
|
786
|
-
if plain_source_file_path.suffix != PLAIN_SOURCE_FILE_EXTENSION:
|
|
787
|
-
raise PlainSyntaxError(
|
|
788
|
-
f"Plain syntax error: Invalid plain file extension: {plain_source_file_path.suffix}. Expected: {PLAIN_SOURCE_FILE_EXTENSION}."
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
module_name = (
|
|
792
|
-
plain_source_file_path.stem
|
|
793
|
-
if plain_source_file_path.is_absolute()
|
|
794
|
-
else plain_source_file_path.with_suffix("").as_posix()
|
|
795
|
-
)
|
|
796
|
-
|
|
797
|
-
code_variables = {}
|
|
798
|
-
|
|
799
|
-
plain_file_parse_result = parse_plain_file(
|
|
800
|
-
module_name,
|
|
801
|
-
code_variables,
|
|
802
|
-
template_dirs,
|
|
803
|
-
imported_modules=[],
|
|
804
|
-
modules_trace=[],
|
|
805
|
-
)
|
|
806
|
-
|
|
791
|
+
def validate_and_marshall_module(
|
|
792
|
+
plain_file_parse_result: PlainFileParseResult,
|
|
793
|
+
module_name: str,
|
|
794
|
+
exported_definitions: list[mistletoe.block_token.token],
|
|
795
|
+
code_variables: dict,
|
|
796
|
+
) -> dict:
|
|
797
|
+
"""Every check a module must pass after parsing, and its marshalled plain source tree."""
|
|
807
798
|
if len(plain_file_parse_result.required_concepts) > 0:
|
|
808
799
|
missing_required_concepts_msg = "Missing required concepts: "
|
|
809
800
|
missing_required_concepts_msg += ", ".join(plain_file_parse_result.required_concepts)
|
|
@@ -831,14 +822,6 @@ def plain_file_parser( # noqa: C901
|
|
|
831
822
|
has_requires=bool(plain_file_parse_result.required_modules),
|
|
832
823
|
)
|
|
833
824
|
|
|
834
|
-
exported_definitions = process_required_modules(
|
|
835
|
-
plain_file_parse_result.required_modules,
|
|
836
|
-
code_variables={},
|
|
837
|
-
template_dirs=template_dirs,
|
|
838
|
-
all_required_modules=[],
|
|
839
|
-
modules_trace=[],
|
|
840
|
-
)
|
|
841
|
-
|
|
842
825
|
process_exported_definitions(plain_file_parse_result.plain_source, exported_definitions)
|
|
843
826
|
|
|
844
827
|
process_acceptance_tests(plain_file_parse_result.plain_source)
|
|
@@ -858,4 +841,64 @@ def plain_file_parser( # noqa: C901
|
|
|
858
841
|
if plain_spec.DEFINITIONS in marshalled_plain_source:
|
|
859
842
|
concept_utils.sort_definitions(marshalled_plain_source[plain_spec.DEFINITIONS])
|
|
860
843
|
|
|
861
|
-
return
|
|
844
|
+
return marshalled_plain_source
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def _parse_module(
|
|
848
|
+
plain_source_file_name: str, template_dirs: list[str]
|
|
849
|
+
) -> tuple[str, dict, list[str], list[tuple[str, dict]]]:
|
|
850
|
+
plain_source_file_path = Path(plain_source_file_name)
|
|
851
|
+
if plain_source_file_path.suffix != PLAIN_SOURCE_FILE_EXTENSION:
|
|
852
|
+
raise PlainSyntaxError(
|
|
853
|
+
f"Plain syntax error: Invalid plain file extension: {plain_source_file_path.suffix}. Expected: {PLAIN_SOURCE_FILE_EXTENSION}."
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
module_name = (
|
|
857
|
+
plain_source_file_path.stem
|
|
858
|
+
if plain_source_file_path.is_absolute()
|
|
859
|
+
else plain_source_file_path.with_suffix("").as_posix()
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
# code_variables are populated when liquid templating is applied and consumed by process_code_variables
|
|
863
|
+
# once the plain source is marshalled.
|
|
864
|
+
code_variables: dict = {}
|
|
865
|
+
|
|
866
|
+
plain_file_parse_result = parse_plain_file(
|
|
867
|
+
module_name,
|
|
868
|
+
code_variables,
|
|
869
|
+
template_dirs,
|
|
870
|
+
imported_modules=[],
|
|
871
|
+
modules_trace=[],
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
chain: list[tuple[str, dict]] = []
|
|
875
|
+
exported_definitions = process_required_modules(
|
|
876
|
+
plain_file_parse_result.required_modules,
|
|
877
|
+
template_dirs=template_dirs,
|
|
878
|
+
all_required_modules=[],
|
|
879
|
+
modules_trace=[],
|
|
880
|
+
chain=chain,
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
marshalled_plain_source = validate_and_marshall_module(
|
|
884
|
+
plain_file_parse_result, module_name, exported_definitions, code_variables
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
return module_name, marshalled_plain_source, plain_file_parse_result.required_modules, chain
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def plain_file_parser(plain_source_file_name: str, template_dirs: list[str]) -> tuple[str, dict, list[str]]:
|
|
891
|
+
"""Parse a module, validating it and every module in its ``requires`` chain."""
|
|
892
|
+
module_name, marshalled_plain_source, required_modules, _ = _parse_module(plain_source_file_name, template_dirs)
|
|
893
|
+
return module_name, marshalled_plain_source, required_modules
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def parse_module_chain(plain_file_name: str, template_dirs: list[str]) -> list[tuple[str, dict]]:
|
|
897
|
+
"""Parse a module and every module in its ``requires`` chain.
|
|
898
|
+
|
|
899
|
+
Returns ``(module name, marshalled plain source tree)`` pairs: every required module
|
|
900
|
+
first, deepest ancestors first, then the module itself. A module reached through more
|
|
901
|
+
than one ``requires`` path appears once, at its first (deepest) position.
|
|
902
|
+
"""
|
|
903
|
+
module_name, marshalled_plain_source, _, chain = _parse_module(plain_file_name, template_dirs)
|
|
904
|
+
return chain + [(module_name, marshalled_plain_source)]
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
something named {{ variable_name | code_variable }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Not a plain file.
|