From 983989d989384e92808fd7a200566ab4c8155b17 Mon Sep 17 00:00:00 2001 From: Dimitri Merejkowsky Date: Thu, 13 Aug 2020 16:20:28 +0200 Subject: [PATCH] Use `argh` for dispatch instead of argparse --- docs/faq.md | 6 +- mypy.ini | 3 + poetry.lock | 14 +++- pyproject.toml | 1 + tsrc/cli/__init__.py | 46 ++++++++--- tsrc/cli/apply_manifest.py | 25 ++++-- tsrc/cli/foreach.py | 94 +++++++++++++++++---- tsrc/cli/init.py | 39 ++++++--- tsrc/cli/log.py | 32 ++++++-- tsrc/cli/main.py | 139 ++++---------------------------- tsrc/cli/status.py | 24 +++++- tsrc/cli/sync.py | 28 +++++-- tsrc/cli/version.py | 4 +- tsrc/test/test_resolve_repos.py | 27 ++----- 14 files changed, 271 insertions(+), 211 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 9da91fc7..e6aa7fe3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -92,10 +92,10 @@ That being said: `--verbose` flag, like so: `tsrc --verbose sync` -# Why argparse? +# Why argh? -See [docopt v argparse](https://dmerej.info/blog/post/docopt-v-argparse/), and -[please don't use click](http://xion.io/post/programming/python-dont-use-click.html). +Because we need (almost) all of `argparse` features, but still want to keep the +code DRY. # Why YAML? diff --git a/mypy.ini b/mypy.ini index 138941cd..947c438f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -14,6 +14,9 @@ warn_return_any = true ignore_missing_imports = false pretty = true +[mypy-argh] +ignore_missing_imports = true + [mypy-colored_traceback] ignore_missing_imports = true diff --git a/poetry.lock b/poetry.lock index a810f1bc..ed856944 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,14 @@ optional = false python-versions = "*" version = "1.4.4" +[[package]] +category = "main" +description = "An unobtrusive argparse wrapper with natural syntax" +name = "argh" +optional = false +python-versions = "*" +version = "0.26.2" + [[package]] category = "dev" description = "Atomic file writes." @@ -739,7 +747,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "6da38a3ae42e78e13ff1b5db2a7480b4931fb7b931cb53b9c613954e4567ba56" +content-hash = "dec729abe4ffa6c31d1c460d82873251302c467184898ce01e514fb374d7b790" lock-version = "1.0" python-versions = "^3.6" @@ -752,6 +760,10 @@ appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +argh = [ + {file = "argh-0.26.2-py2.py3-none-any.whl", hash = "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3"}, + {file = "argh-0.26.2.tar.gz", hash = "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, diff --git a/pyproject.toml b/pyproject.toml index 4e0f930e..cc5ce595 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ requests = "^2.22.0" schema = "^0.7.1" tabulate = "^0.8.6" unidecode = "^1.1.1" +argh = "^0.26.2" [tool.poetry.dev-dependencies] # Tests diff --git a/tsrc/cli/__init__.py b/tsrc/cli/__init__.py index 652d09b3..20080249 100644 --- a/tsrc/cli/__init__.py +++ b/tsrc/cli/__init__.py @@ -1,9 +1,9 @@ """ Common tools for tsrc commands """ -from typing import List -import argparse +from typing import List, Optional import os +from argh import arg import cli_ui as ui from path import Path @@ -13,6 +13,24 @@ from tsrc.manifest import Manifest +with_workspace = arg( + "-w", + "--workspace", + dest="workspace_path", + help="path to the current workspace", + type=Path, +) +with_groups = arg( + "-g", "--group", "--groups", nargs="+", dest="groups", help="groups to use" +) +with_all_cloned = arg( + "--all-cloned", + action="store_true", + dest="all_cloned", + help="run on all cloned repos", +) + + def find_workspace_path() -> Path: """ Look for a workspace root somewhere in the upper directories hierarchy @@ -30,21 +48,23 @@ def find_workspace_path() -> Path: raise tsrc.Error("Could not find current workspace") -def get_workspace(args: argparse.Namespace) -> tsrc.Workspace: - if args.workspace_path: - workspace_path = Path(args.workspace_path) - else: +def get_workspace(workspace_path: Optional[Path]) -> tsrc.Workspace: + if not workspace_path: workspace_path = find_workspace_path() return tsrc.Workspace(workspace_path) -def get_workspace_with_repos(args: argparse.Namespace) -> tsrc.Workspace: - workspace = get_workspace(args) - workspace.repos = resolve_repos(workspace, args=args) +def get_workspace_with_repos( + workspace_path: Path, groups: Optional[List[str]], all_cloned: bool +) -> tsrc.Workspace: + workspace = get_workspace(workspace_path) + workspace.repos = resolve_repos(workspace, groups, all_cloned) return workspace -def resolve_repos(workspace: Workspace, args: argparse.Namespace) -> List[tsrc.Repo]: +def resolve_repos( + workspace: Workspace, groups: Optional[List[str]], all_cloned: bool +) -> List[tsrc.Repo]: """" Given a workspace with its config and its local manifest, and a collection of parsed command line arguments, @@ -52,10 +72,10 @@ def resolve_repos(workspace: Workspace, args: argparse.Namespace) -> List[tsrc.R """ # Handle --all-cloned and --groups manifest = workspace.get_manifest() - if args.groups: - return manifest.get_repos(groups=args.groups) + if groups: + return manifest.get_repos(groups=groups) - if args.all_cloned: + if all_cloned: repos = manifest.get_repos(all_=True) return [repo for repo in repos if (workspace.root_path / repo.dest).exists()] diff --git a/tsrc/cli/apply_manifest.py b/tsrc/cli/apply_manifest.py index 19ce5a91..59b90a72 100644 --- a/tsrc/cli/apply_manifest.py +++ b/tsrc/cli/apply_manifest.py @@ -1,18 +1,29 @@ """ Entry point for `tsrc apply-manifest` """ -import argparse +from typing import Optional +from argh import arg +from path import Path import cli_ui as ui -import tsrc.cli +import tsrc.manifest +from tsrc.cli import ( + with_workspace, + get_workspace, + repos_from_config, +) -def main(args: argparse.Namespace) -> None: - workspace = tsrc.cli.get_workspace(args) - manifest_path = args.manifest_path - ui.info_1("Applying manifest from", args.manifest_path) +@with_workspace # type: ignore +@arg("manifest_path", help="path to the local manifest", type=Path) # type: ignore +def apply_manifest(manifest_path: Path, workspace_path: Optional[Path] = None) -> None: + """ apply a local manifest file """ + workspace = get_workspace(workspace_path) + + ui.info_1("Applying manifest from", manifest_path) + manifest = tsrc.manifest.load(manifest_path) - workspace.repos = tsrc.cli.repos_from_config(manifest, workspace.config) + workspace.repos = repos_from_config(manifest, workspace.config) workspace.clone_missing() workspace.set_remotes() workspace.perform_filesystem_operations() diff --git a/tsrc/cli/foreach.py b/tsrc/cli/foreach.py index bfc07621..98a1161a 100644 --- a/tsrc/cli/foreach.py +++ b/tsrc/cli/foreach.py @@ -1,14 +1,36 @@ """ Entry point for tsrc foreach """ -from typing import List -import argparse +from typing import List, Union, Optional +from argh import arg import subprocess +import textwrap +import sys from path import Path import cli_ui as ui import tsrc -import tsrc.cli +from tsrc.cli import ( + with_workspace, + with_groups, + with_all_cloned, + get_workspace, + resolve_repos, +) + +EPILOG = textwrap.dedent( + """\ + Usage: + # Run command directly + tsrc foreach -- some-cmd --with-option + Or: + # Run command through the shell + tsrc foreach -c 'some cmd' + """ +) + + +Command = Union[str, List[str]] class CommandFailed(tsrc.Error): @@ -21,18 +43,22 @@ class CouldNotStartProcess(tsrc.Error): class CmdRunner(tsrc.Task[tsrc.Repo]): def __init__( - self, workspace_path: Path, cmd: List[str], cmd_as_str: str, shell: bool = False + self, + workspace_path: Path, + command: Command, + description: str, + shell: bool = False, ) -> None: self.workspace_path = workspace_path - self.cmd = cmd - self.cmd_as_str = cmd_as_str + self.command = command + self.description = description self.shell = shell def display_item(self, repo: tsrc.Repo) -> str: return repo.dest def on_start(self, *, num_items: int) -> None: - ui.info_1(f"Running `{self.cmd_as_str}` on {num_items} repos") + ui.info_1(f"Running `{self.description}` on {num_items} repos") def on_failure(self, *, num_errors: int) -> None: ui.error(f"Command failed for {num_errors} repo(s)") @@ -45,24 +71,64 @@ def process(self, index: int, count: int, repo: tsrc.Repo) -> None: # fmt: off ui.info( ui.lightgray, "$ ", - ui.reset, ui.bold, self.cmd_as_str, + ui.reset, ui.bold, self.description, sep="" ) # fmt: on full_path = self.workspace_path / repo.dest try: - rc = subprocess.call(self.cmd, cwd=full_path, shell=self.shell) + rc = subprocess.call(self.command, cwd=full_path, shell=self.shell) except OSError as e: raise CouldNotStartProcess("Error when starting process:", e) if rc != 0: raise CommandFailed() -def main(args: argparse.Namespace) -> None: - workspace = tsrc.cli.get_workspace_with_repos(args) - cmd_runner = CmdRunner( - workspace.root_path, args.cmd, args.cmd_as_str, shell=args.shell - ) +def die(message: str) -> None: + ui.error(message) + print(EPILOG, end="") + sys.exit(1) + + +@with_workspace # type: ignore +@with_groups # type: ignore +@with_all_cloned # type: ignore +@arg("cmd", help="command to run", nargs="*") # type: ignore +@arg("-c", help="use a shell to run the command", dest="shell") # type: ignore +def foreach( + cmd: List[str], + workspace_path: Optional[Path] = None, + groups: Optional[List[str]] = None, + all_cloned: bool = False, + shell: bool = False, +) -> None: + """ run the same command on several repositories """ + # Note: + # we want to support both: + # $ tsrc foreach -c 'shell command' + # and + # $ tsrc foreach -- some-cmd --some-opts + # + # Due to argparse limitations, cmd will always be a list, + # but we need a *string* when using 'shell=True' + # + # So transform use the value from `cmd` and `shell` to build: + # * `subprocess_cmd`, suitable as argument to pass to subprocess.run() + # * `cmd_as_str`, suitable for display purposes + command: Command = [] + if shell: + if len(cmd) != 1: + die("foreach -c must be followed by exactly one argument") + command = cmd[0] + description = cmd[0] + else: + if not cmd: + die("needs a command to run") + command = cmd + description = " ".join(cmd) + workspace = get_workspace(workspace_path) + workspace.repos = resolve_repos(workspace, groups=groups, all_cloned=all_cloned) + cmd_runner = CmdRunner(workspace.root_path, command, description, shell=shell) tsrc.run_sequence(workspace.repos, cmd_runner) ui.info("OK", ui.check) diff --git a/tsrc/cli/init.py b/tsrc/cli/init.py index 9c131e79..7bf9d3a1 100644 --- a/tsrc/cli/init.py +++ b/tsrc/cli/init.py @@ -1,18 +1,33 @@ """ Entry point for `tsrc init` """ -import argparse +from typing import List, Optional import os -from path import Path +from argh import arg import cli_ui as ui +from path import Path import tsrc -from tsrc.cli import repos_from_config +from tsrc.cli import repos_from_config, with_workspace, with_groups from tsrc.workspace import Workspace from tsrc.workspace.config import WorkspaceConfig - -def main(args: argparse.Namespace) -> None: - path_as_str = args.workspace_path or os.getcwd() +remote_help = "only use this remote when cloning repositories" + + +@with_workspace # type: ignore +@with_groups # type: ignore +@arg("-r", "--singular-remote", help=remote_help) # type: ignore +def init( + url: str, + workspace_path: Optional[Path] = None, + groups: Optional[List[str]] = None, + branch: str = "master", + clone_all_repos: bool = False, + shallow: bool = False, + singular_remote: Optional[str] = None, +) -> None: + """ initialize a new workspace""" + path_as_str = workspace_path or os.getcwd() workspace_path = Path(path_as_str) cfg_path = workspace_path / ".tsrc" / "config.yml" @@ -22,12 +37,12 @@ def main(args: argparse.Namespace) -> None: ui.info_1("Configuring workspace in", ui.bold, workspace_path) workspace_config = WorkspaceConfig( - manifest_url=args.url, - manifest_branch=args.branch, - clone_all_repos=args.clone_all_repos, - repo_groups=args.groups, - shallow_clones=args.shallow, - singular_remote=args.remote, + manifest_url=url, + manifest_branch=branch, + clone_all_repos=clone_all_repos, + repo_groups=groups or [], + shallow_clones=shallow, + singular_remote=singular_remote, ) workspace_config.save_to_file(cfg_path) diff --git a/tsrc/cli/log.py b/tsrc/cli/log.py index 636f70d2..2b5095fc 100644 --- a/tsrc/cli/log.py +++ b/tsrc/cli/log.py @@ -1,15 +1,37 @@ """ Entry point for tsrc log """ -import argparse +from typing import List, Optional +from argh import arg +from path import Path import cli_ui as ui import tsrc -import tsrc.cli +from tsrc.cli import ( + with_workspace, + with_groups, + with_all_cloned, + get_workspace, + resolve_repos, +) -def main(args: argparse.Namespace) -> None: - workspace = tsrc.cli.get_workspace_with_repos(args) + +@with_workspace # type: ignore +@with_groups # type: ignore +@with_all_cloned # type: ignore +@arg("--from", dest="from_", metavar="FROM", help="from ref") # type: ignore +@arg("--to", help="to ref") # type: ignore +def log( + workspace_path: Optional[Path] = None, + groups: Optional[List[str]] = None, + all_cloned: bool = False, + to: str = "", + from_: str = "", +) -> None: + """ show a combine git log for several repositories """ + workspace = get_workspace(workspace_path) + workspace.repos = resolve_repos(workspace, groups=groups, all_cloned=all_cloned) all_ok = True for repo in workspace.repos: full_path = workspace.root_path / repo.dest @@ -25,7 +47,7 @@ def main(args: argparse.Namespace) -> None: "log", "--color=always", f"--pretty=format:{log_format}", - f"{args.from_}...{args.to}", + f"{from_}...{to}", ] rc, out = tsrc.git.run_captured(full_path, *cmd, check=False) if rc != 0: diff --git a/tsrc/cli/main.py b/tsrc/cli/main.py index 80a02bd9..ea58e36c 100644 --- a/tsrc/cli/main.py +++ b/tsrc/cli/main.py @@ -1,68 +1,29 @@ """ Main tsrc entry point """ import argparse +import argh import functools -import importlib import os import sys -import textwrap from typing import Callable, Optional, Sequence import colored_traceback import cli_ui as ui -from path import Path import tsrc +from .apply_manifest import apply_manifest +from .foreach import foreach +from .init import init +from .log import log +from .status import status +from .sync import sync +from .version import version + ArgsList = Optional[Sequence[str]] MainFunc = Callable[..., None] -def fix_cmd_args_for_foreach( - args: argparse.Namespace, foreach_parser: argparse.ArgumentParser -) -> None: - """ We want to support both: - $ tsrc foreach -c 'shell command' - and - $ tsrc foreach -- some-cmd --some-opts - - Due to argparse limitations, args.cmd will always be - a list, but we nee a *string* when using 'shell=True' - - So transform the argparse.Namespace object to have - * args.cmd suitable to pass to subprocess later - * args.cmd_as_str suitable for display purposes - - """ - - def die(message: str) -> None: - ui.error(message) - print(foreach_parser.epilog, end="") - sys.exit(1) - - if args.shell: - if len(args.cmd) != 1: - die("foreach -c must be followed by exactly one argument") - cmd = args.cmd[0] - cmd_as_str = cmd - else: - cmd = args.cmd - if not cmd: - die("needs a command to run") - cmd_as_str = " ".join(cmd) - - args.cmd = cmd - args.cmd_as_str = cmd_as_str - - -def add_workspace_subparser( - subparser: argparse._SubParsersAction, name: str -) -> argparse.ArgumentParser: - parser = subparser.add_parser(name) - parser.add_argument("-w", "--workspace", dest="workspace_path") - return parser - - def main_wrapper(main_func: MainFunc) -> MainFunc: """ Wraps main() entry point to better deal with errors """ @@ -105,89 +66,21 @@ def main(args: ArgsList = None) -> None: main_impl(args=args) -def add_group_options(parser: argparse.ArgumentParser) -> None: - parser.add_argument("-g", "--group", "--groups", nargs="+", dest="groups") - parser.add_argument("--all-cloned", action="store_true", dest="all_cloned") - - def main_impl(args: ArgsList = None) -> None: - parser = argparse.ArgumentParser(prog="tsrc") + parser = argh.ArghParser(prog="tsrc") parser.add_argument( "--version", action="version", version="tsrc " + tsrc.__version__ ) - parser.add_argument("--verbose", help="Show debug messages", action="store_true") + parser.add_argument("--verbose", help="show debug messages", action="store_true") parser.add_argument( - "-q", "--quiet", help="Only display warnings and errors", action="store_true" + "-q", "--quiet", help="only display warnings and errors", action="store_true" ) parser.add_argument("--color", choices=["auto", "always", "never"]) - subparsers = parser.add_subparsers(title="subcommands", dest="command") - - subparsers.add_parser("version") - - foreach_parser = add_workspace_subparser(subparsers, "foreach") - add_group_options(foreach_parser) - foreach_parser.add_argument("cmd", nargs="*") - foreach_parser.add_argument("-c", dest="shell", action="store_true") - foreach_parser.epilog = textwrap.dedent( - """\ - Usage: - # Run command directly - tsrc foreach -- some-cmd --with-option - Or: - # Run command through the shell - tsrc foreach -c 'some cmd' - """ - ) - foreach_parser.formatter_class = argparse.RawDescriptionHelpFormatter - - init_parser = add_workspace_subparser(subparsers, "init") - add_group_options(init_parser) - init_parser.add_argument("url") - init_parser.add_argument("-b", "--branch") - init_parser.add_argument( - "--clone-all-repos", - action="store_true", - dest="clone_all_repos", - help="clone all repos from the manifest, regardless of groups", - ) - init_parser.add_argument( - "-s", "--shallow", action="store_true", dest="shallow", default=False - ) - init_parser.add_argument( - "-r", - "--remote", - dest="remote", - help="Use only the remote with this name to clone repositories", - ) - init_parser.set_defaults(branch="master") - - log_parser = add_workspace_subparser(subparsers, "log") - add_group_options(log_parser) - log_parser.add_argument("--from", required=True, dest="from_", metavar="FROM") - log_parser.add_argument("--to") - log_parser.set_defaults(to="HEAD") - - status_parser = add_workspace_subparser(subparsers, "status") - add_group_options(status_parser) - - sync_parser = add_workspace_subparser(subparsers, "sync") - add_group_options(sync_parser) - sync_parser.add_argument("--force", action="store_true") - - apply_manifest = add_workspace_subparser(subparsers, "apply-manifest") - apply_manifest.add_argument("manifest_path", type=Path) - - args_ns = parser.parse_args(args=args) # type: argparse.Namespace - setup_ui(args_ns) + parser.add_commands([apply_manifest, init, foreach, version, log, sync, status]) - command = args_ns.command - if not command: - parser.print_help() - sys.exit(1) - module = importlib.import_module("tsrc.cli." + command.replace("-", "_")) - if command == "foreach": - fix_cmd_args_for_foreach(args_ns, foreach_parser) + ui_args = parser.parse_args(args=args) + setup_ui(ui_args) - return module.main(args_ns) # type: ignore + parser.dispatch(argv=args) diff --git a/tsrc/cli/status.py b/tsrc/cli/status.py index ac06b74f..be49bab5 100644 --- a/tsrc/cli/status.py +++ b/tsrc/cli/status.py @@ -2,17 +2,24 @@ from typing import Dict, List, Union, Optional, Tuple -import argparse import collections import shutil import cli_ui as ui +from path import Path import tsrc import tsrc.errors -import tsrc.cli import tsrc.git +from tsrc.cli import ( + with_workspace, + with_groups, + with_all_cloned, + get_workspace, + resolve_repos, +) + class ManifestStatus: def __init__(self, repo: tsrc.Repo, *, manifest: tsrc.Manifest): @@ -106,7 +113,16 @@ def on_success(self) -> None: ui.info(*message) -def main(args: argparse.Namespace) -> None: - workspace = tsrc.cli.get_workspace_with_repos(args) +@with_workspace # type: ignore +@with_groups # type: ignore +@with_all_cloned # type: ignore +def status( + workspace_path: Optional[Path] = None, + groups: Optional[List[str]] = None, + all_cloned: bool = False, +) -> None: + """ display workspace status summary """ + workspace = get_workspace(workspace_path) + workspace.repos = resolve_repos(workspace, groups=groups, all_cloned=all_cloned) status_collector = StatusCollector(workspace) tsrc.run_sequence(workspace.repos, status_collector) diff --git a/tsrc/cli/sync.py b/tsrc/cli/sync.py index 53201086..e2c0d77d 100644 --- a/tsrc/cli/sync.py +++ b/tsrc/cli/sync.py @@ -1,20 +1,36 @@ """ Entry point for tsrc sync """ -import argparse +from typing import List, Optional import cli_ui as ui +from path import Path -import tsrc.cli +from tsrc.cli import ( + with_workspace, + with_groups, + with_all_cloned, + get_workspace, + resolve_repos, +) -def main(args: argparse.Namespace) -> None: - workspace = tsrc.cli.get_workspace(args) +@with_workspace # type: ignore +@with_groups # type: ignore +@with_all_cloned # type: ignore +def sync( + workspace_path: Optional[Path] = None, + groups: Optional[List[str]] = None, + all_cloned: bool = False, + force: bool = False, +) -> None: + """ synchronize the current workspace with the manifest """ + workspace = get_workspace(workspace_path) ui.info_2("Updating manifest") workspace.update_manifest() - workspace.repos = tsrc.cli.resolve_repos(workspace, args=args) + workspace.repos = resolve_repos(workspace, groups=groups, all_cloned=all_cloned) workspace.clone_missing() workspace.set_remotes() - workspace.sync(force=args.force) + workspace.sync(force=force) workspace.perform_filesystem_operations() ui.info("Done", ui.check) diff --git a/tsrc/cli/version.py b/tsrc/cli/version.py index 5b8fe11f..64ede864 100644 --- a/tsrc/cli/version.py +++ b/tsrc/cli/version.py @@ -1,8 +1,8 @@ """ Entry point for tsrc version """ -import argparse import tsrc -def main(args: argparse.Namespace) -> None: +def version() -> None: + """ show version number """ print("tsrc version", tsrc.__version__) diff --git a/tsrc/test/test_resolve_repos.py b/tsrc/test/test_resolve_repos.py index 006552ed..c0c13431 100644 --- a/tsrc/test/test_resolve_repos.py +++ b/tsrc/test/test_resolve_repos.py @@ -1,7 +1,5 @@ from typing import Any, Dict, List, Optional -import argparse - from path import Path import ruamel.yaml @@ -42,12 +40,6 @@ def create_workspace( return Workspace(tmp_path) -def new_args( - *, all_cloned: bool = False, groups: Optional[List[str]] = None -) -> argparse.Namespace: - return argparse.Namespace(all_cloned=all_cloned, groups=groups) - - def repo_names(repos: List[Repo]) -> List[str]: return [repo.dest for repo in repos] @@ -62,9 +54,7 @@ def test_no_args_no_config_no_default_group(tmp_path: Path) -> None: """ create_manifest(tmp_path, repos=["foo", "bar"]) workspace = create_workspace(tmp_path) - args = new_args() - - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=None, all_cloned=False) assert repo_names(actual) == ["foo", "bar"] @@ -82,9 +72,8 @@ def test_no_args_no_config_default_group(tmp_path: Path) -> None: } create_manifest(tmp_path, repos=["foo", "outside"], groups=groups) workspace = create_workspace(tmp_path) - args = new_args() - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=None, all_cloned=False) assert repo_names(actual) == ["foo"] @@ -102,9 +91,8 @@ def test_no_args_workspace_configured_with_all_repos(tmp_path: Path) -> None: } create_manifest(tmp_path, repos=["foo", "outside"], groups=groups) workspace = create_workspace(tmp_path, clone_all_repos=True) - args = new_args() - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=None, all_cloned=False) assert repo_names(actual) == ["foo", "outside"] @@ -123,9 +111,8 @@ def test_no_args_workspace_configured_with_some_groups(tmp_path: Path) -> None: } create_manifest(tmp_path, repos=["foo", "bar"], groups=groups) workspace = create_workspace(tmp_path, repo_groups=["group1"]) - args = new_args() - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=None, all_cloned=False) assert repo_names(actual) == ["foo"] @@ -144,9 +131,8 @@ def test_groups_requested(tmp_path: Path) -> None: } create_manifest(tmp_path, repos=["foo", "bar"], groups=groups) workspace = create_workspace(tmp_path, repo_groups=["group1"]) - args = new_args(groups=["group1"]) - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=["group1"], all_cloned=False) assert repo_names(actual) == ["foo"] @@ -165,10 +151,9 @@ def test_all_cloned_requested(tmp_path: Path) -> None: } create_manifest(tmp_path, repos=["foo", "bar", "other"], groups=groups) workspace = create_workspace(tmp_path, repo_groups=["group1"]) - args = new_args(all_cloned=True) (tmp_path / "foo").makedirs_p() (tmp_path / "other").makedirs_p() - actual = resolve_repos(workspace, args=args) + actual = resolve_repos(workspace, groups=None, all_cloned=True) assert repo_names(actual) == ["foo", "other"]