From 04efc31d75e35b50ef77c29bf9975f7cc0cc1bb1 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:27:15 +0200 Subject: [PATCH] Add support for validating paths outside the current directory, fix validating a directory without `cfbs.json` resulting in a traceback Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/cfbs_config.py | 25 ----------------------- cfbs/cfbs_json.py | 27 +++++++++++++++++++++++- cfbs/commands.py | 50 ++++++++++++++++++++++++++++++++++++++++++++- cfbs/git.py | 2 +- cfbs/main.py | 2 +- cfbs/utils.py | 2 ++ cfbs/validate.py | 1 + 7 files changed, 80 insertions(+), 29 deletions(-) diff --git a/cfbs/cfbs_config.py b/cfbs/cfbs_config.py index a04d14cb..29613f51 100644 --- a/cfbs/cfbs_config.py +++ b/cfbs/cfbs_config.py @@ -536,29 +536,4 @@ def _input_list(input_data): "Unsupported input type '%s'" % definition["type"] ) - def _get_all_module_names(self, search_in=("build", "provides", "index")): - modules = [] - - if "build" in search_in and "build" in self: - modules.extend((x["name"] for x in self["build"])) - if "provides" in search_in and "provides" in self: - modules.extend(self["provides"].keys()) - if "index" in search_in: - modules.extend(self.index.keys()) - - return modules - - def can_reach_dependency(self, name, search_in=("build", "provides", "index")): - return name in self._get_all_module_names(search_in) - - def find_module(self, name, search_in=("build", "provides", "index")): - if "build" in search_in and "build" in self: - for module in self["build"]: - if module["name"] == name: - return module - if "provides" in search_in and "provides" in self and name in self["provides"]: - return self["provides"][name] - if "index" in search_in and name in self.index: - return self.index[name] - return None diff --git a/cfbs/cfbs_json.py b/cfbs/cfbs_json.py index 7f34bde7..4e649090 100644 --- a/cfbs/cfbs_json.py +++ b/cfbs/cfbs_json.py @@ -8,7 +8,7 @@ def _construct_provided_module(name, data, url, commit, added_by="cfbs add"): - # At this point the @commmit part should be removed from url so: + # At this point the @commit part should be removed from url so: # either url should not have an @, # or the @ should be for user@host.something assert "@" not in url or url.rindex(".") > url.rindex("@") @@ -112,6 +112,31 @@ def warn_about_unknown_keys(self): + "pip3 install --upgrade cfbs" ) + def _get_all_module_names(self, search_in=("build", "provides", "index")): + modules = [] + + if "build" in search_in and "build" in self: + modules.extend((x["name"] for x in self["build"])) + if "provides" in search_in and "provides" in self: + modules.extend(self["provides"].keys()) + if "index" in search_in: + modules.extend(self.index.keys()) + + return modules + + def can_reach_dependency(self, name, search_in=("build", "provides", "index")): + return name in self._get_all_module_names(search_in) + + def find_module(self, name, search_in=("build", "provides", "index")): + if "build" in search_in and "build" in self: + for module in self["build"]: + if module["name"] == name: + return module + if "provides" in search_in and "provides" in self and name in self["provides"]: + return self["provides"][name] + if "index" in search_in and name in self.index: + return self.index[name] + def get(self, key, default=None): if not self._data: # If the specified JSON file does not exist return default diff --git a/cfbs/commands.py b/cfbs/commands.py index 2f42d9dc..3d336ba1 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -698,7 +698,55 @@ def update_command(to_update) -> Result: @cfbs_command("validate") -def validate_command() -> int: +def validate_command(paths=None, index_arg=None) -> int: + if paths: + ret_value = 0 + + for path in paths: + # `path` can be either a directory containing a CFBS project (cfbs.json) or path to cfbs.json + if not os.path.basename(path) == "cfbs.json": + if path.endswith(".json"): + log.warning( + "Only cfbs.json files can be validated. Skipping validation" + ) + # if the user actually has an e.g. directory ending with `.json`, + # they can always specify the path to the cfbs.json in that folder to force the validation + continue + # assume the provided path is a project directory + path = os.path.join(path, "cfbs.json") + + if not os.path.isfile(path): + # either cfbs.json doesn't exist, or it's an e.g. directory + if not os.path.exists(path): + log.warning( + "%s is not a valid CFBS project path, skipping validation" + % path + ) + else: + log.warning( + "A non-file named cfbs.json detected. Skipping validation. " + + "If it is not a mistake that cfbs.json is not a file, specify the full path to the cfbs.json file inside to validate." + ) + continue + + config = CFBSJson(path=path, index_argument=index_arg) + + r = validate_config(config) + if r != 0: + log.warning("Validation of project at path %s failed" % path) + ret_value = 1 + else: + print("Successfully validated the project at path", path) + + return ret_value + + if not is_cfbs_repo(): + # TODO change GenericExitError to UserError here + raise GenericExitError( + "Cannot validate: this is not a CFBS project. " + + "Use `cfbs init` to start a new project in this directory, or provide a path to a CFBS project to validate." + ) + config = CFBSConfig.get_instance() return validate_config(config) diff --git a/cfbs/git.py b/cfbs/git.py index 98d141e4..9bf77e54 100644 --- a/cfbs/git.py +++ b/cfbs/git.py @@ -127,7 +127,7 @@ def git_commit( :param commit_msg: commit message to use for the commit :param scope: files to include in the commit or `"all"` (`git commit -a`) :type scope: str or an iterable of str - :param edit_commit_message=False: whether the user should be prompted to edit and + :param edit_commit_msg=False: whether the user should be prompted to edit and save the commit message or not :param user_name: override git config user name :param user_email: override git config user email diff --git a/cfbs/main.py b/cfbs/main.py index 140f8c2b..440b2463 100644 --- a/cfbs/main.py +++ b/cfbs/main.py @@ -154,7 +154,7 @@ def _main() -> Union[int, Result]: if args.command == "pretty": return commands.pretty_command(args.args, args.check, args.keep_order) if args.command == "validate": - return commands.validate_command() + return commands.validate_command(args.args, args.index) if args.command in ("info", "show"): return commands.info_command(args.args) diff --git a/cfbs/utils.py b/cfbs/utils.py index a84817c2..32c4da74 100644 --- a/cfbs/utils.py +++ b/cfbs/utils.py @@ -169,6 +169,8 @@ def read_json(path) -> Union[OrderedDict, None]: return json.loads(f.read(), object_pairs_hook=OrderedDict) except FileNotFoundError: return None + except IsADirectoryError: + return None except NotADirectoryError: return None except json.decoder.JSONDecodeError as ex: diff --git a/cfbs/validate.py b/cfbs/validate.py index 740efc56..09b56511 100644 --- a/cfbs/validate.py +++ b/cfbs/validate.py @@ -232,6 +232,7 @@ def _validate_config(config, empty_build_list_ok=False): def validate_config(config, empty_build_list_ok=False): + """Returns `0` if there are no validation errors, and `1` otherwise.""" try: _validate_config(config, empty_build_list_ok) return 0