From ba233a0cbad76b4783a03893e7bf4716fbc0f0ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 26 Jun 2024 08:24:58 +0200 Subject: Merging upstream version 24.6.1. Signed-off-by: Daniel Baumann --- src/ansiblelint/cli.py | 89 +++++++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 34 deletions(-) (limited to 'src/ansiblelint/cli.py') diff --git a/src/ansiblelint/cli.py b/src/ansiblelint/cli.py index c9178a7..ce8d9ec 100644 --- a/src/ansiblelint/cli.py +++ b/src/ansiblelint/cli.py @@ -1,4 +1,5 @@ """CLI parser setup and helpers.""" + from __future__ import annotations import argparse @@ -7,7 +8,7 @@ import os import sys from argparse import Namespace from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from ansiblelint.config import ( DEFAULT_KINDS, @@ -16,7 +17,7 @@ from ansiblelint.config import ( Options, log_entries, ) -from ansiblelint.constants import CUSTOM_RULESDIR_ENVVAR, DEFAULT_RULESDIR, RC +from ansiblelint.constants import CUSTOM_RULESDIR_ENVVAR, DEFAULT_RULESDIR, EPILOG, RC from ansiblelint.file_utils import ( Lintable, abspath, @@ -29,7 +30,7 @@ from ansiblelint.schemas.main import validate_file_schema from ansiblelint.yaml_utils import clean_json if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable, Sequence _logger = logging.getLogger(__name__) @@ -91,7 +92,7 @@ def load_config(config_file: str | None) -> tuple[dict[Any, Any], str | None]: config = clean_json(config_lintable.data) if not isinstance(config, dict): msg = "Schema failed to properly validate the config file." - raise RuntimeError(msg) + raise TypeError(msg) config["config_file"] = config_path config_dir = os.path.dirname(config_path) expand_to_normalized_paths(config, config_dir) @@ -134,7 +135,7 @@ class AbspathArgAction(argparse.Action): values: str | Sequence[Any] | None, option_string: str | None = None, ) -> None: - if isinstance(values, (str, Path)): + if isinstance(values, str | Path): values = [values] if values: normalized_values = [ @@ -145,7 +146,7 @@ class AbspathArgAction(argparse.Action): class WriteArgAction(argparse.Action): - """Argparse action to handle the --write flag with optional args.""" + """Argparse action to handle the --fix flag with optional args.""" _default = "__default__" @@ -174,8 +175,8 @@ class WriteArgAction(argparse.Action): super().__init__( option_strings=option_strings, dest=dest, - nargs="?", # either 0 (--write) or 1 (--write=a,b,c) argument - const=self._default, # --write (no option) implicitly stores this + nargs="?", # either 0 (--fix) or 1 (--fix=a,b,c) argument + const=self._default, # --fix (no option) implicitly stores this default=default, type=type, choices=choices, @@ -194,8 +195,8 @@ class WriteArgAction(argparse.Action): lintables = getattr(namespace, "lintables", None) if not lintables and isinstance(values, str): # args are processed in order. - # If --write is after lintables, then that is not ambiguous. - # But if --write comes first, then it might actually be a lintable. + # If --fix is after lintables, then that is not ambiguous. + # But if --fix comes first, then it might actually be a lintable. maybe_lintable = Path(values) if maybe_lintable.exists(): namespace.lintables = [values] @@ -211,26 +212,40 @@ class WriteArgAction(argparse.Action): setattr(namespace, self.dest, values) @classmethod - def merge_write_list_config( + def merge_fix_list_config( cls, from_file: list[str], from_cli: list[str], ) -> list[str]: - """Combine the write_list from file config with --write CLI arg. + """Determine the write_list value based on cli vs config. + + When --fix is not passed from command line the from_cli is an empty list, + so we use the file. - Handles the implicit "all" when "__default__" is present and file config is empty. + When from_cli is not an empty list, we ignore the from_file value. """ - if not from_file or "none" in from_cli: - # --write is the same as --write=all - return ["all" if value == cls._default else value for value in from_cli] - # --write means use the config from the config file - from_cli = [value for value in from_cli if value != cls._default] - return from_file + from_cli + if not from_file: + arguments = ["all"] if from_cli == [cls._default] else from_cli + else: + arguments = from_file + for magic_value in ("none", "all"): + if magic_value in arguments and len(arguments) > 1: + msg = f"When passing '{magic_value}' to '--fix', you cannot pass other values." + raise RuntimeError( + msg, + ) + if len(arguments) == 1 and arguments[0] == "none": + arguments = [] + return arguments def get_cli_parser() -> argparse.ArgumentParser: """Initialize an argument parser.""" - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + epilog=EPILOG, + # Avoid rewrapping description and epilog + formatter_class=argparse.RawTextHelpFormatter, + ) listing_group = parser.add_mutually_exclusive_group() listing_group.add_argument( @@ -338,22 +353,16 @@ def get_cli_parser() -> argparse.ArgumentParser: help="Return non-zero exit code on warnings as well as errors", ) parser.add_argument( - "--write", + "--fix", dest="write_list", # this is a tri-state argument that takes an optional comma separated list: action=WriteArgAction, - help="Allow ansible-lint to reformat YAML files and run rule transforms " - "(Reformatting YAML files standardizes spacing, quotes, etc. " - "A rule transform can fix or simplify fixing issues identified by that rule). " + help="Allow ansible-lint to perform auto-fixes, including YAML reformatting. " "You can limit the effective rule transforms (the 'write_list') by passing a " "keywords 'all' or 'none' or a comma separated list of rule ids or rule tags. " - "YAML reformatting happens whenever '--write' or '--write=' is used. " - "'--write' and '--write=all' are equivalent: they allow all transforms to run. " - "The effective list of transforms comes from 'write_list' in the config file, " - "followed whatever '--write' args are provided on the commandline. " - "'--write=none' resets the list of transforms to allow reformatting YAML " - "without running any of the transforms (ie '--write=none,rule-id' will " - "ignore write_list in the config file and only run the rule-id transform).", + "YAML reformatting happens whenever '--fix' or '--fix=' is used. " + "'--fix' and '--fix=all' are equivalent: they allow all transforms to run. " + "Presence of --fix in command overrides config file value.", ) parser.add_argument( "--show-relpath", @@ -490,6 +499,7 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options: "enable_list": [], "only_builtins_allow_collections": [], "only_builtins_allow_modules": [], + "supported_ansible_also": [], # do not include "write_list" here. See special logic below. } @@ -506,6 +516,10 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options: for entry, default in lists_map.items(): if not getattr(cli_config, entry, None): setattr(cli_config, entry, default) + if cli_config.write_list is None: + cli_config.write_list = [] + elif cli_config.write_list == [WriteArgAction._default]: # noqa: SLF001 + cli_config.write_list = ["all"] return cli_config for entry in bools: @@ -513,8 +527,8 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options: v = getattr(cli_config, entry) or file_value setattr(cli_config, entry, v) - for entry, default in scalar_map.items(): - file_value = file_config.pop(entry, default) + for entry, default_scalar in scalar_map.items(): + file_value = file_config.pop(entry, default_scalar) v = getattr(cli_config, entry, None) or file_value setattr(cli_config, entry, v) @@ -533,7 +547,7 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options: setattr( cli_config, entry, - WriteArgAction.merge_write_list_config( + WriteArgAction.merge_fix_list_config( from_file=file_config.pop(entry, []), from_cli=getattr(cli_config, entry, []) or [], ), @@ -557,6 +571,13 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options: def get_config(arguments: list[str]) -> Options: """Extract the config based on given args.""" parser = get_cli_parser() + # translate deprecated options + for i, value in enumerate(arguments): + if arguments[i].startswith("--write"): + arguments[i] = value.replace("--write", "--fix") + _logger.warning( + "Replaced deprecated '--write' option with '--fix', change you call to avoid future regressions when we remove old option.", + ) options = Options(**vars(parser.parse_args(arguments))) # docs is not document, being used for internal documentation building -- cgit v1.2.3