Skip to content

Commit 0a984cd

Browse files
committed
refactor!(cli): click -> argparse
1 parent 11a3a01 commit 0a984cd

File tree

3 files changed

+246
-238
lines changed

3 files changed

+246
-238
lines changed

src/vcspull/cli/__init__.py

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,52 @@
44
~~~~~~~~~~~
55
66
"""
7+
import argparse
78
import logging
89

9-
import click
10-
1110
from libvcs.__about__ import __version__ as libvcs_version
1211

1312
from ..__about__ import __version__
1413
from ..log import setup_logger
15-
from .sync import sync
14+
from .sync import create_sync_subparser, sync
1615

1716
log = logging.getLogger(__name__)
1817

1918

20-
@click.group(
21-
context_settings={
22-
"obj": {},
23-
"help_option_names": ["-h", "--help"],
24-
}
25-
)
26-
@click.option(
27-
"--log-level",
28-
default="INFO",
29-
help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
30-
)
31-
@click.version_option(
32-
__version__,
33-
"-V",
34-
"--version",
35-
message=f"%(prog)s %(version)s, libvcs {libvcs_version}",
36-
)
37-
def cli(log_level):
38-
setup_logger(log=log, level=log_level.upper())
39-
40-
41-
# Register sub-commands here
42-
cli.add_command(sync)
19+
def create_parser():
20+
parser = argparse.ArgumentParser(prog="vcspull")
21+
parser.add_argument(
22+
"--version",
23+
"-V",
24+
action="version",
25+
version=f"%(prog)s {__version__}, libvcs {libvcs_version}",
26+
)
27+
parser.add_argument(
28+
"--log-level",
29+
action="store",
30+
default="INFO",
31+
help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
32+
)
33+
subparsers = parser.add_subparsers(dest="subparser_name")
34+
sync_parser = subparsers.add_parser("sync")
35+
create_sync_subparser(sync_parser)
36+
37+
return parser
38+
39+
40+
def cli(args=None):
41+
parser = create_parser()
42+
args = parser.parse_args(args)
43+
44+
setup_logger(log=log, level=args.log_level.upper())
45+
46+
if args.subparser_name is None:
47+
parser.print_help()
48+
return
49+
elif args.subparser_name == "sync":
50+
sync(
51+
repo_terms=args.repo_terms,
52+
config=args.config,
53+
exit_on_error=args.exit_on_error,
54+
parser=parser,
55+
)

src/vcspull/cli/sync.py

Lines changed: 42 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,17 @@
1+
import argparse
12
import logging
23
import sys
4+
import typing as t
35
from copy import deepcopy
46

5-
import click
6-
import click.shell_completion
7-
from click.shell_completion import CompletionItem
8-
97
from libvcs._internal.shortcuts import create_project
108
from libvcs.url import registry as url_tools
11-
from vcspull.types import ConfigDict
129

1310
from ..config import filter_repos, find_config_files, load_configs
1411

1512
log = logging.getLogger(__name__)
1613

1714

18-
def get_repo_completions(
19-
ctx: click.Context, param: click.Parameter, incomplete: str
20-
) -> list[CompletionItem]:
21-
configs = (
22-
load_configs(find_config_files(include_home=True))
23-
if ctx.params["config"] is None
24-
else load_configs(files=[ctx.params["config"]])
25-
)
26-
found_repos: list[ConfigDict] = []
27-
repo_terms = [incomplete]
28-
29-
for repo_term in repo_terms:
30-
dir, vcs_url, name = None, None, None
31-
if any(repo_term.startswith(n) for n in ["./", "/", "~", "$HOME"]):
32-
dir = dir
33-
elif any(repo_term.startswith(n) for n in ["http", "git", "svn", "hg"]):
34-
vcs_url = repo_term
35-
else:
36-
name = repo_term
37-
38-
# collect the repos from the config files
39-
found_repos.extend(filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name))
40-
if len(found_repos) == 0:
41-
found_repos = configs
42-
43-
return [
44-
CompletionItem(o["name"])
45-
for o in found_repos
46-
if o["name"].startswith(incomplete)
47-
]
48-
49-
50-
def get_config_file_completions(ctx, args, incomplete):
51-
return [
52-
click.shell_completion.CompletionItem(c)
53-
for c in find_config_files(include_home=True)
54-
if str(c).startswith(incomplete)
55-
]
56-
57-
5815
def clamp(n, _min, _max):
5916
return max(_min, min(n, _max))
6017

@@ -63,68 +20,64 @@ def clamp(n, _min, _max):
6320
NO_REPOS_FOR_TERM_MSG = 'No repo found in config(s) for "{name}"'
6421

6522

66-
@click.command(name="sync")
67-
@click.pass_context
68-
@click.argument(
69-
"repo_terms", type=click.STRING, nargs=-1, shell_complete=get_repo_completions
70-
)
71-
@click.option(
72-
"config",
73-
"--config",
74-
"-c",
75-
type=click.Path(exists=True),
76-
help="Specify config",
77-
shell_complete=get_config_file_completions,
78-
)
79-
@click.option(
80-
"exit_on_error",
81-
"--exit-on-error",
82-
"-x",
83-
is_flag=True,
84-
default=False,
85-
help="Exit immediately when encountering an error syncing multiple repos",
86-
)
87-
def sync(ctx, repo_terms, config, exit_on_error: bool) -> None:
23+
def create_sync_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
24+
parser.add_argument("--config", "-c", help="Specify config")
25+
parser.add_argument("repo_terms", nargs="+", help="Specify config")
26+
parser.add_argument(
27+
"--exit-on-error",
28+
"-x",
29+
action="store_true",
30+
dest="exit_on_error",
31+
help="Specify config",
32+
)
33+
return parser
34+
35+
36+
def sync(
37+
repo_terms,
38+
config,
39+
exit_on_error: bool,
40+
parser: t.Optional[
41+
argparse.ArgumentParser
42+
] = None, # optional so sync can be unit tested
43+
) -> None:
8844
if config:
8945
configs = load_configs([config])
9046
else:
9147
configs = load_configs(find_config_files(include_home=True))
9248
found_repos = []
9349

94-
if repo_terms:
95-
for repo_term in repo_terms:
96-
dir, vcs_url, name = None, None, None
97-
if any(repo_term.startswith(n) for n in ["./", "/", "~", "$HOME"]):
98-
dir = repo_term
99-
elif any(repo_term.startswith(n) for n in ["http", "git", "svn", "hg"]):
100-
vcs_url = repo_term
101-
else:
102-
name = repo_term
103-
104-
# collect the repos from the config files
105-
found = filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
106-
if len(found) == 0:
107-
click.echo(NO_REPOS_FOR_TERM_MSG.format(name=name))
108-
found_repos.extend(
109-
filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
110-
)
111-
else:
112-
click.echo(ctx.get_help(), color=ctx.color)
113-
ctx.exit()
50+
for repo_term in repo_terms:
51+
dir, vcs_url, name = None, None, None
52+
if any(repo_term.startswith(n) for n in ["./", "/", "~", "$HOME"]):
53+
dir = repo_term
54+
elif any(repo_term.startswith(n) for n in ["http", "git", "svn", "hg"]):
55+
vcs_url = repo_term
56+
else:
57+
name = repo_term
58+
59+
# collect the repos from the config files
60+
found = filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
61+
if len(found) == 0:
62+
print(NO_REPOS_FOR_TERM_MSG.format(name=name))
63+
found_repos.extend(filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name))
11464

11565
for repo in found_repos:
11666
try:
11767
update_repo(repo)
11868
except Exception:
119-
click.echo(
69+
print(
12070
f'Failed syncing {repo.get("name")}',
12171
)
12272
if log.isEnabledFor(logging.DEBUG):
12373
import traceback
12474

12575
traceback.print_exc()
12676
if exit_on_error:
127-
raise click.ClickException(EXIT_ON_ERROR_MSG)
77+
if parser is not None:
78+
parser.exit(status=1, message=EXIT_ON_ERROR_MSG)
79+
else:
80+
raise SystemExit(EXIT_ON_ERROR_MSG)
12881

12982

13083
def progress_cb(output, timestamp):

0 commit comments

Comments
 (0)