From 846ac5285452523ca4f1ba3888f8506f26b50ccf Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sun, 28 Jun 2026 01:45:14 +0300 Subject: [PATCH] feat[cli]: Use nice cli --- Readme.md | 2 +- src/cli.py | 24 ++++++++++++++++-------- src/config.py | 2 +- tests/test_cli.py | 38 ++++++++++++++++++++++++++++++++++---- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/Readme.md b/Readme.md index 354f3ce..a4560d3 100644 --- a/Readme.md +++ b/Readme.md @@ -47,7 +47,7 @@ removing the ability to specify a user 3. Install git (`sudo apt install git`) 4. And run it with path to TOML config. ```bash -python gitea-mirror.py config.toml +python gitea-mirror.py --config config.toml ``` diff --git a/src/cli.py b/src/cli.py index af9ccfb..634760a 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,3 +1,4 @@ +import argparse import logging import os.path import sys @@ -20,6 +21,19 @@ from .repository_name import get_repository_path, is_valid_repository_names logger = logging.getLogger(__name__) +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog="gitea-mirror", + description="Mirror Gitea repositories locally.", + ) + parser.add_argument( + "--config", + required=True, + help="Path to TOML configuration file.", + ) + return parser.parse_args(argv) + + def get_fetch_params(config: Config, repo: GiteaRepository): if config.fetch.ssh_key_path: return repo.ssh_url, ssh_git_env(config.fetch.ssh_key_path) @@ -99,15 +113,9 @@ def run(config: Config) -> int: def main(argv: list[str] | None = None) -> int: args = sys.argv[1:] if argv is None else argv - if len(args) < 1: - logging.basicConfig( - level=logging.ERROR, - format="%(levelname)s %(message)s", - ) - logger.error("Usage: gitea-mirror CONFIG_PATH") - return 1 + parsed_args = parse_args(args) try: - config = read_toml_config(args[0]) + config = read_toml_config(parsed_args.config) except RuntimeError as err: logging.basicConfig( level=logging.ERROR, diff --git a/src/config.py b/src/config.py index 03c2592..f77a6d5 100644 --- a/src/config.py +++ b/src/config.py @@ -26,7 +26,7 @@ MAIN_SECTION = "main" def read_toml_config(path: str) -> Config: if not os.path.exists(path): - raise RuntimeError("TOML config path does not exist") + raise RuntimeError(f"TOML config path does not exist, path={path}") try: with open(path, "rb") as config_file: diff --git a/tests/test_cli.py b/tests/test_cli.py index f478f2b..a203d19 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -96,16 +96,46 @@ def test_main_returns_failure_for_invalid_config(monkeypatch): monkeypatch.setattr(cli, "read_toml_config", fake_read_toml_config) - assert cli.main(["bad.toml"]) == 1 + assert cli.main(["--config", "bad.toml"]) == 1 -def test_main_uses_config_path(monkeypatch, tmp_path): +def test_main_uses_config_option(monkeypatch, tmp_path): config = make_config(tmp_path) calls = [] + paths = [] - monkeypatch.setattr(cli, "read_toml_config", lambda path: config) + def fake_read_toml_config(path): + paths.append(path) + return config + + monkeypatch.setattr(cli, "read_toml_config", fake_read_toml_config) monkeypatch.setattr(cli, "setup_logging", lambda logging_config: None) monkeypatch.setattr(cli, "run", lambda config: calls.append(config) or 0) - assert cli.main(["config.toml"]) == 0 + assert cli.main(["--config", "config.toml"]) == 0 + assert paths == ["config.toml"] assert calls == [config] + + +def test_main_requires_config_option(capsys): + try: + cli.main([]) + except SystemExit as err: + assert err.code == 2 + else: + raise AssertionError("expected SystemExit") + + assert "error:" in capsys.readouterr().err + + +def test_main_rejects_positional_config(capsys): + try: + cli.main(["config.toml"]) + except SystemExit as err: + assert err.code == 2 + else: + raise AssertionError("expected SystemExit") + + stderr = capsys.readouterr().err + assert "error:" in stderr + assert "--config" in stderr