-
Notifications
You must be signed in to change notification settings - Fork 0
Introduce simple CLI "check" #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
fe617eb
feat: add parse_module_chain to walk the requires chain
bananaplain ab40862
feat: add plain-parser check command
bananaplain 1acc9fb
test: cover plain-parser check error paths
bananaplain 42df13d
docs: add plain-parser check usage
bananaplain ff0fab4
fix: ignore an empty --template-dir
bananaplain 1a53db9
fix: exit 130 on Ctrl-C instead of printing a traceback
bananaplain 1360970
feat: read template-dir from the config file when --template-dir is n…
bananaplain 881d202
feat: print '<file>: OK' on a valid spec
bananaplain fac25ff
fix: exit 130 on Ctrl-C during config lookup too
bananaplain 2b4ee21
fix: resolve the config file even when --template-dir is given
bananaplain c65c868
test: fail instead of skip when the console script is missing in CI
bananaplain 58a3a5a
refactor: fully validate required modules while parsing the top module
bananaplain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| """Command-line entry point: ``plain-parser check <file.plain> [--template-dir DIR] [--config-name NAME]``.""" | ||
|
|
||
| import argparse | ||
| import os | ||
| import sys | ||
|
|
||
| import yaml | ||
|
|
||
| from plain_parser import loaders, plain_file, plain_spec | ||
|
|
||
| DEFAULT_CONFIG_NAME = "config.yaml" | ||
| TEMPLATE_DIR_CONFIG_KEYS = ("template_dir", "template-dir") | ||
|
|
||
|
|
||
| class AmbiguousConfigFileError(Exception): | ||
| pass | ||
|
|
||
|
|
||
| def build_parser() -> argparse.ArgumentParser: | ||
| parser = argparse.ArgumentParser(prog="plain-parser", description="Tools for ***plain specification files.") | ||
| subparsers = parser.add_subparsers(dest="command", required=True) | ||
|
|
||
| check_parser = subparsers.add_parser("check", help="Validate a .plain module and its requires chain.") | ||
| check_parser.add_argument("plain_file", help="Path to the .plain module.") | ||
| check_parser.add_argument( | ||
| "--template-dir", | ||
| help="Directory searched for modules and templates after the .plain file's own directory. " | ||
| "Overrides the template-dir value of the config file.", | ||
| ) | ||
| check_parser.add_argument( | ||
| "--config-name", | ||
| default=DEFAULT_CONFIG_NAME, | ||
| help="Name of the config file to read template-dir from. Looked up in the .plain file's directory " | ||
| "and the current working directory. Defaults to %(default)s.", | ||
| ) | ||
| return parser | ||
|
|
||
|
|
||
| def resolve_config_file(config_name: str, plain_file_path: str) -> str | None: | ||
| """The config file next to the .plain file, else the one in the working directory, else None.""" | ||
| plain_file_dir = os.path.dirname(os.path.abspath(plain_file_path)) | ||
| plain_dir_config = os.path.normpath(os.path.join(plain_file_dir, config_name)) | ||
| cwd_config = os.path.normpath(os.path.join(os.getcwd(), config_name)) | ||
|
|
||
| in_plain_dir = os.path.exists(plain_dir_config) | ||
| in_cwd = os.path.exists(cwd_config) | ||
| if in_plain_dir and in_cwd and plain_dir_config != cwd_config: | ||
| raise AmbiguousConfigFileError( | ||
| f"Config file '{config_name}' was found in two locations:\n" | ||
| f" - Plain file directory: {plain_file_dir}\n" | ||
| f" - Current working directory: {os.getcwd()}\n" | ||
| f"Remove the config file from one of these locations to resolve the ambiguity." | ||
| ) | ||
| if in_plain_dir: | ||
| return plain_dir_config | ||
| if in_cwd: | ||
| return cwd_config | ||
| return None | ||
|
|
||
|
|
||
| def template_dir_from_config(config_file: str) -> str | None: | ||
| """The template-dir value of the config file, resolved against the file's directory. Other keys are ignored.""" | ||
| with open(config_file) as f: | ||
| config = yaml.safe_load(f) or {} | ||
|
|
||
| for key in TEMPLATE_DIR_CONFIG_KEYS: | ||
| value = config.get(key) | ||
| if value: | ||
| value = os.path.expanduser(str(value)) | ||
| return value if os.path.isabs(value) else os.path.join(os.path.dirname(config_file), value) | ||
| return None | ||
|
|
||
|
|
||
| def check(plain_file_path: str, template_dir: str | None) -> None: | ||
| """Raise if the module, its requires chain, or its linked resources are invalid.""" | ||
| template_dirs = [os.path.dirname(os.path.abspath(plain_file_path))] | ||
| if template_dir: | ||
| template_dirs.append(template_dir) | ||
|
|
||
| chain = plain_file.parse_module_chain(os.path.basename(plain_file_path), template_dirs) | ||
|
|
||
| for module_name, plain_source_tree in chain: | ||
| resources_list: list[dict] = [] | ||
| plain_spec.collect_linked_resources(plain_source_tree, resources_list, None, True) | ||
| loaders.load_linked_resources(template_dirs, resources_list, module_name) | ||
|
|
||
| _, top_plain_source_tree = chain[-1] | ||
| for frid in plain_spec.get_frids(top_plain_source_tree): | ||
| plain_spec.get_specifications_for_frid(top_plain_source_tree, frid) | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| parser = build_parser() | ||
| args = parser.parse_args(argv) | ||
|
|
||
| try: | ||
| # The config file is resolved even when --template-dir is given, so an ambiguous config is always an error. | ||
| config_template_dir = _template_dir_from_config(parser, args) | ||
| check(args.plain_file, args.template_dir or config_template_dir) | ||
| except KeyboardInterrupt: | ||
| return 130 | ||
| except Exception as e: | ||
| print(f"Error: {e}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
| print(f"{args.plain_file}: OK") | ||
| return 0 | ||
|
|
||
|
|
||
| def _template_dir_from_config(parser: argparse.ArgumentParser, args: argparse.Namespace) -> str | None: | ||
| try: | ||
| config_file = resolve_config_file(args.config_name, args.plain_file) | ||
| return template_dir_from_config(config_file) if config_file is not None else None | ||
| except AmbiguousConfigFileError as e: | ||
| parser.error(str(e)) | ||
| except Exception as e: | ||
| parser.error(f"Error reading config file: {e}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| ***implementation reqs*** | ||
|
|
||
| - An implementation requirement. | ||
|
|
||
| ***functional specs*** | ||
|
|
||
| - A functionality. | ||
|
|
||
| ***acceptance tests*** | ||
| - Behaves as described in [the binary](binary.bin). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| ***implementation reqs*** | ||
|
|
||
| - An implementation requirement. | ||
|
|
||
| ***functional specs*** | ||
|
|
||
| - A functionality. | ||
|
|
||
| ***acceptance tests*** | ||
| - Behaves as described in [the notes](notes.md). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ***implementation reqs*** | ||
|
|
||
| - Follow [the sample](../sample_base64_image.txt). | ||
|
|
||
| ***functional specs*** | ||
|
|
||
| - A functionality. |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ***implementation reqs*** | ||
|
|
||
| - Follow [the binary](binary.bin). | ||
|
|
||
| ***functional specs*** | ||
|
|
||
| - A functionality. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for the two functions that call the same
_parse_modulecall, with different return signatures:parse_module_chainreturns every module's tree and the CLI (top + required).plain_file_parseronly returns the top module's treeplain_file_parserkeeps the 3-tuple return so the contract withcodeplainholds until the upcoming refactorThe load steps in
cli.pyline 84, 85, remain and they're just walks over a list now - no module is actually re-read or re-parsed.