From f13b09c3170a3c8b25e6c0e71e794e8755218160 Mon Sep 17 00:00:00 2001 From: mpabi Date: Sun, 26 Apr 2026 15:26:24 +0200 Subject: [PATCH] Add tabular rvctl command overview --- README.md | 9 ++++ doc/rvctl.md | 25 +++++++++ rvctl.py | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) diff --git a/README.md b/README.md index 561d7dd..5387d0b 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,13 @@ rvctl.py `rvctl` jest jedynym publicznym entrypointem. `rvctl.py` jest implementacja uruchamiana przez wrapper i nie wymaga osobnego wywolywania przez ucznia. +Uruchomienie bez argumentow pokazuje tabelaryczny skrot komend: + +```bash +./rvctl +./rvctl tokens +``` + ## Autoryzacja Masz dwie drogi. @@ -253,9 +260,11 @@ cd rv-launcher Przyklady: ```bash +./rvctl ./rvctl show-config ./rvctl list-series ./rvctl list-cards inf +./rvctl tokens ./rvctl submission inf 03 --class 4i --nick u1 ./rvctl tmux-container inf 03 --dry-run ./rvctl tmux-container inf 03 --session rv-inf03 --attach diff --git a/doc/rvctl.md b/doc/rvctl.md index a5cf0cc..c7e639c 100644 --- a/doc/rvctl.md +++ b/doc/rvctl.md @@ -65,6 +65,14 @@ rvctl.py `rvctl` jest jedynym publicznym entrypointem. `rvctl.py` jest implementacja uruchamiana przez wrapper i nie wymaga osobnego wywolywania przez ucznia. +Uruchomienie bez argumentow pokazuje tabelaryczny skrot komend: + +```bash +./rvctl +./rvctl tokens +./rvctl help tokens +``` + ### Autoryzacja Masz dwie drogi. @@ -174,6 +182,23 @@ Globalne przelaczniki: - `--config PATH` Uzywa innego pliku `workspace.json`. +Pomoc tabelaryczna: + +```bash +./rvctl +./rvctl help +./rvctl tokens +./rvctl help tokens +``` + +Szczegolowy help parsera: + +```bash +./rvctl --help +./rvctl --help +./rvctl tokens --help +``` + Komendy: - `show-config` diff --git a/rvctl.py b/rvctl.py index 9495a6c..ecfd137 100644 --- a/rvctl.py +++ b/rvctl.py @@ -1108,6 +1108,140 @@ def add_store_selection_options(parser: argparse.ArgumentParser) -> None: parser.add_argument("--token-name", help="Token name inside the selected user entry, for example 't1'.") +def print_table(headers: list[str], rows: list[list[str]]) -> None: + widths = [len(header) for header in headers] + for row in rows: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + + print(" ".join(header.ljust(widths[index]) for index, header in enumerate(headers))) + print(" ".join("-" * width for width in widths)) + for row in rows: + print(" ".join(value.ljust(widths[index]) for index, value in enumerate(row))) + + +def print_config_summary(config_path: Path) -> None: + try: + config = load_config(config_path) + except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as error: + print("Config:") + print_table( + ["field", "value"], + [ + ["config", str(config_path)], + ["status", f"unavailable: {error}"], + ], + ) + return + + print("Config:") + print_table( + ["field", "value"], + [ + ["config", str(config.config_path)], + ["workspace", str(config.workspace_root)], + ["series", str(config.series_root)], + ["tokens", str(config.token_path)], + ], + ) + + +def print_main_overview(config_path: Path) -> None: + print("RVCTL - launcher workspace RV") + print() + print("Usage:") + print(" ./rvctl [options]") + print(" ./rvctl tokens [options]") + print(" ./rvctl --help") + print() + print("Commands:") + print_table( + ["command", "arguments", "purpose"], + [ + ["show-config", "", "show resolved paths and Git defaults"], + ["list-series", "", "list series from workspace"], + ["list-cards", "[series]", "list cards in a selected series"], + ["tmux-container", "[series] [card]", "start tmux with container in pane 0"], + ["submission", "[series] [card] --class K --nick N", "prepare answer repo and student branch"], + ["tokens", "scan|read|stats|write|update", "manage tokens.json and Git remote credentials"], + ], + ) + print() + print("Typical flow:") + print_table( + ["step", "command", "result"], + [ + ["1", "./rvctl tokens scan", "read credentials from remotes if present"], + ["2", "./rvctl list-series", "choose a series"], + ["3", "./rvctl list-cards inf", "choose a card"], + ["4", "./rvctl tmux-container inf ", "start the working container"], + ["5", "./rvctl submission inf --class 4i --nick u1", "prepare answer remote and branch"], + ], + ) + print() + print_config_summary(config_path) + print() + print("Docs:") + print(" doc/rvctl.md") + print(" doc/tokens.md") + print(" doc/workspace.md") + + +def print_tokens_overview() -> None: + print("RVCTL tokens - token store and Git remote credentials") + print() + print("Usage:") + print(" ./rvctl tokens [options]") + print(" ./rvctl tokens --help") + print() + print("Commands:") + print_table( + ["command", "common options", "direction", "purpose"], + [ + ["scan", "[--repo PATH]", "repo -> tokens.json", "scan remotes and store credentials found in URLs"], + ["read", "[--server ENDPOINT]", "tokens.json", "show servers, users and token names"], + ["stats", "[--repo PATH]", "repo + tokens.json", "compare remote URLs with local token store"], + ["write", "--remote R [--replace]", "tokens.json -> repo", "write selected token into a remote URL"], + ["update", "--from remotes|store", "selected", "run scan or write with explicit direction"], + ], + ) + print() + print("Examples:") + print_table( + ["case", "command"], + [ + ["scan current repo", "./rvctl tokens scan"], + ["scan selected card", "./rvctl tokens scan --repo ~/dev/workspace/rv/series/inf/03"], + ["compare state", "./rvctl tokens stats --repo ~/dev/workspace/rv/series/inf/03"], + ["write auth to r1", "./rvctl tokens write --repo PATH --remote r1 --server URL --user u1 --token-name t1"], + ], + ) + + +def extract_config_path(argv: list[str]) -> tuple[Path, list[str]]: + config_path = Path(__file__).with_name("workspace.json") + remaining: list[str] = [] + index = 0 + while index < len(argv): + arg = argv[index] + if arg == "--config": + if index + 1 >= len(argv): + remaining.append(arg) + index += 1 + continue + config_path = Path(argv[index + 1]) + index += 2 + continue + if arg.startswith("--config="): + config_path = Path(arg.split("=", 1)[1]) + index += 1 + continue + remaining.append(arg) + index += 1 + + return config_path, remaining + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="rvctl", @@ -1223,6 +1357,14 @@ def build_parser() -> argparse.ArgumentParser: def main() -> int: + config_path, command_args = extract_config_path(sys.argv[1:]) + if not command_args or command_args == ["help"]: + print_main_overview(config_path) + return 0 + if command_args in (["tokens"], ["help", "tokens"], ["tokens", "help"]): + print_tokens_overview() + return 0 + parser = build_parser() args = parser.parse_args() config = load_config(args.config)