Source code for project_config.__main__

"""Command line interface."""

from __future__ import annotations

import argparse
import os
import sys
import typing as t
from gettext import gettext as _

from importlib_metadata_argparse_version import ImportlibMetadataVersionAction

from project_config.exceptions import ProjectConfigException
from project_config.project import Project
from project_config.reporters import POSSIBLE_REPORTER_IDS, parse_reporter_id


SPHINX_IS_RUNNING = "sphinx" in sys.modules
OPEN_QUOTE_CHAR = "”" if SPHINX_IS_RUNNING else '"'
CLOSE_QUOTE_CHAR = "”" if SPHINX_IS_RUNNING else '"'


[docs]class ReporterAction(argparse.Action): """Custom argparse action for reporter CLI option."""
[docs] def _raise_invalid_reporter_error(self, reporter_id: str) -> None: raise argparse.ArgumentError( self, _("invalid choice: %(value)r (choose from %(choices)s)") % { "value": reporter_id, "choices": ", ".join( [f"'{rep}'" for rep in POSSIBLE_REPORTER_IDS], ), }, )
def __call__( # type: ignore # noqa: D102 self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, value: str, option_string: str, ) -> None: reporter: t.Dict[str, t.Any] = {} if value: try: reporter_name, reporter_kwargs = parse_reporter_id(value) except Exception: self._raise_invalid_reporter_error(value) reporter_id = reporter_name if reporter_kwargs["fmt"]: reporter_id += f':{reporter_kwargs["fmt"]}' if reporter_id not in POSSIBLE_REPORTER_IDS: self._raise_invalid_reporter_error(reporter_id) reporter["name"] = reporter_name reporter["kwargs"] = reporter_kwargs namespace.reporter = reporter
[docs]def _controlled_error( show_traceback: bool, exc: Exception, message: str, ) -> int: if show_traceback: raise exc sys.stderr.write(f"{message}\n") return 1
[docs]def build_main_parser() -> argparse.ArgumentParser: # noqa: D103 parser = argparse.ArgumentParser( description=( "Validate the configuration of your project against the" " configured styles." ), prog="project-config", add_help=False, ) parser.add_argument( "-h", "--help", action="help", help="Show project-config's help and exit.", ) parser.add_argument( "-v", "--version", action=ImportlibMetadataVersionAction, help="Show project-config's version number and exit.", importlib_metadata_version_from="project-config", ) # common arguments parser.add_argument( "-T", "--traceback", action="store_true", help=( "Display the full traceback when a exception is found." " Useful for debugging purposes." ), ) parser.add_argument( "-c", "--config", type=str, help="Custom configuration file path.", ) parser.add_argument( "--root", "--rootdir", dest="rootdir", type=str, help=( "Root directory of the project. Useful if you want to" " execute project-config for another project rather than the" " current working directory." ), ) possible_reporters_msg = ", ".join( [f"'{rep}'" for rep in POSSIBLE_REPORTER_IDS], ) example = ( f"{OPEN_QUOTE_CHAR}file{CLOSE_QUOTE_CHAR}:" f"{OPEN_QUOTE_CHAR}blue{CLOSE_QUOTE_CHAR}" ) parser.add_argument( "-r", "--reporter", action=ReporterAction, default={}, metavar="NAME[:FORMAT];OPTION=VALUE", help=( "Reporter for generated output when failed. Possible values" f" are {possible_reporters_msg}. Additionally, options can be" " passed to the reporter appending ';' to the end of the reporter" " id with the syntax '<OPTION>=<JSON VALUE>'. Console reporters can" " take an argument 'color' which accepts a JSON object to customize" " the colors for parts of the report like files, for example:" " table:simple;color={%s}." % example ), ) parser.add_argument( "--no-color", "--nocolor", dest="color", action="store_false", help=( "Disable colored output. You can also set a value in" " the environment variable NO_COLOR." ), ) parser.add_argument( "--no-cache", "--nocache", dest="cache", action="store_false", help=( "Disable cache for the current execution. You can also set" " the value 'false' in the environment variable" " PROJECT_CONFIG_USE_CACHE." ), ) parser.add_argument( "command", choices=["check", "show", "clean"], help="Command to execute.", ) return parser
[docs]def _parse_command_args( command: str, subcommand_args: t.List[str], ) -> t.Tuple[argparse.Namespace, t.List[str]]: if command in ("show", "clean"): if command == "show": parser = argparse.ArgumentParser(prog="project-config show") parser.add_argument( "data", choices=["config", "style", "cache", "plugins"], help=( "Indicate which data must be shown, discovered" " configuration, extended style or cache directory" " location." ), ) else: # command == "clean" parser = argparse.ArgumentParser(prog="project-config clean") parser.add_argument( "data", choices=["cache"], help=( "Indicate which data must be cleaned. Currently, only" " 'cache' is the possible data to clean." ), ) args, remaining = parser.parse_known_args(subcommand_args) else: args = argparse.Namespace() remaining = subcommand_args return args, remaining
[docs]def parse_cli_args_and_subargs( # noqa: D103 parser: argparse.ArgumentParser, argv: t.List[str], ) -> t.Tuple[argparse.Namespace, argparse.Namespace]: args, subcommand_args = parser.parse_known_args(argv) subargs, remaining = _parse_command_args(args.command, subcommand_args) if remaining: parser.print_help() raise SystemExit(1) return args, subargs
[docs]def parse_args( # noqa: D103 argv: t.List[str], ) -> t.Tuple[argparse.Namespace, argparse.Namespace]: args, subargs = parse_cli_args_and_subargs(build_main_parser(), argv) if args.cache is False: os.environ["PROJECT_CONFIG_USE_CACHE"] = "false" return args, subargs
[docs]def run(argv: t.List[str] = []) -> int: # noqa: D103 os.environ["PROJECT_CONFIG"] = "true" args, subargs = parse_args(argv) try: project = Project( args.config, args.rootdir, args.reporter, args.color, ) getattr(project, args.command)(subargs) except ProjectConfigException as exc: return _controlled_error(args.traceback, exc, exc.message) except FileNotFoundError as exc: # pragma: no cover return _controlled_error( args.traceback, exc, f"{exc.args[1]} '{exc.filename}'", ) return 0
[docs]def main() -> None: # noqa: D103 # pragma: no cover raise SystemExit(run(sys.argv[1:]))
if __name__ == "__main__": main()