diff --git a/apollo-cli.py b/apollo-cli.py index 48f1d14..c201a73 100755 --- a/apollo-cli.py +++ b/apollo-cli.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 +import argparse import json import os import string -import sys import urllib.error import urllib.request from http.cookies import SimpleCookie @@ -10,22 +10,11 @@ API_BASE = os.environ.get("APOLLO_SERVER", "http://127.0.0.1:8080").rstrip("/") + "/api" DEFAULT_PUZZLE_VALUE = 32 +PROG = "apollo-cli.py" -def usage(): - print( - "usage: apollo-cli.py [args...]\n" - "\n" - "set_admin_password \n" - "create_puzzle \n" - "join \n" - "auth_state \n" - "submit \n" - "logout \n" - "mock_puzzles \n" - "mock_join ", - file=sys.stderr, - ) +def cli_error(message): + raise SystemExit(f"{PROG}: error: {message}") def cookie_path(username): @@ -43,11 +32,9 @@ def load_user_cookie(username): with open(path, "r", encoding="utf-8") as f: sid = f.read().strip() except FileNotFoundError: - print(f"missing cookie file: {path}", file=sys.stderr) - raise SystemExit(1) + cli_error(f"missing cookie file: {path}") if not sid: - print(f"empty cookie file: {path}", file=sys.stderr) - raise SystemExit(1) + cli_error(f"empty cookie file: {path}") return sid @@ -91,11 +78,10 @@ def request_json(method, path, payload=None, sid=None): if text: print_response_text(text) else: - print(f"HTTP {err.code}", file=sys.stderr) + cli_error(f"HTTP {err.code}") raise SystemExit(1) except urllib.error.URLError as err: - print(f"request failed: {err.reason}", file=sys.stderr) - raise SystemExit(1) + cli_error(f"request failed: {err.reason}") def extract_sid(set_cookie_headers): @@ -104,135 +90,134 @@ def extract_sid(set_cookie_headers): cookie.load(header) if "sid" in cookie: return cookie["sid"].value - print("missing sid cookie in response", file=sys.stderr) - raise SystemExit(1) + cli_error("missing sid cookie in response") def cmd_set_admin_password(args): - if len(args) != 2: - usage() - raise SystemExit(1) - _, _, text = request_json("POST", "/set_admin_password", {"init_password": args[0], "password": args[1]}) + _, _, text = request_json( + "POST", + "/set_admin_password", + {"init_password": args.initial_password, "password": args.password}, + ) print_response_text(text) def cmd_create_puzzle(args): - if len(args) != 3: - usage() - raise SystemExit(1) - puzzle_id, solution, password = args payload = { "puzzle_solutions": { - puzzle_id: { - "solution": solution, + args.id: { + "solution": args.solution, "value": DEFAULT_PUZZLE_VALUE, } }, - "password": password, + "password": args.password, } _, _, text = request_json("POST", "/set_solution", payload) print_response_text(text) def cmd_join(args): - if len(args) != 1: - usage() - raise SystemExit(1) - username = args[0] - _, headers, text = request_json("POST", "/join", {"username": username}) + _, headers, text = request_json("POST", "/join", {"username": args.username}) sid = extract_sid(headers.get_all("Set-Cookie", [])) - save_user_cookie(username, sid) + save_user_cookie(args.username, sid) print_response_text(text) def cmd_auth_state(args): - if len(args) != 1: - usage() - raise SystemExit(1) - sid = load_user_cookie(args[0]) + sid = load_user_cookie(args.username) _, _, text = request_json("GET", "/auth_state", sid=sid) print_response_text(text) def cmd_submit(args): - if len(args) != 3: - usage() - raise SystemExit(1) - username, puzzle_id, solution = args - sid = load_user_cookie(username) - payload = {"puzzle_id": puzzle_id, "solution": solution} + sid = load_user_cookie(args.username) + payload = {"puzzle_id": args.id, "solution": args.solution} _, _, text = request_json("POST", "/submit", payload, sid=sid) print_response_text(text) def cmd_logout(args): - if len(args) != 1: - usage() - raise SystemExit(1) - username = args[0] - sid = load_user_cookie(username) + sid = load_user_cookie(args.username) _, _, text = request_json("POST", "/logout", {}, sid=sid) - delete_user_cookie(username) + delete_user_cookie(args.username) print_response_text(text) def cmd_mock_puzzles(args): - if len(args) != 3: - usage() - raise SystemExit(1) - start, end, password = args - start_int = int(start) - end_int = int(end) - for current in range(start_int, end_int + 1): - cmd_create_puzzle([str(current), str(current * 10), password]) + for current in range(args.from_id_int, args.to_id_int + 1): + cmd_create_puzzle( + argparse.Namespace( + id=str(current), solution=str(current * 10), password=args.password + ) + ) def cmd_mock_join(args): - if len(args) != 2: - usage() - raise SystemExit(1) - start_char, end_char = args - if ( - len(start_char) != 1 - or len(end_char) != 1 - or start_char not in string.ascii_lowercase - or end_char not in string.ascii_lowercase - ): - print("mock_join expects lowercase ascii characters", file=sys.stderr) - raise SystemExit(1) - if start_char > end_char: - print("mock_join expects <= ", file=sys.stderr) - raise SystemExit(1) - start_idx = string.ascii_lowercase.index(start_char) - end_idx = string.ascii_lowercase.index(end_char) + if args.from_char > args.to_char: + cli_error("mock_join expects <= ") + start_idx = string.ascii_lowercase.index(args.from_char) + end_idx = string.ascii_lowercase.index(args.to_char) for username in string.ascii_lowercase[start_idx : end_idx + 1]: - cmd_join([f"user-{username}"]) + cmd_join(argparse.Namespace(username=f"user-{username}")) -COMMANDS = { - "set_admin_password": cmd_set_admin_password, - "create_puzzle": cmd_create_puzzle, - "join": cmd_join, - "auth_state": cmd_auth_state, - "submit": cmd_submit, - "logout": cmd_logout, - "mock_puzzles": cmd_mock_puzzles, - "mock_join": cmd_mock_join, -} +def lower_char(value): + if len(value) != 1 or value not in string.ascii_lowercase: + raise argparse.ArgumentTypeError("expected a lowercase ascii character") + return value -def main(): - if len(sys.argv) < 2: - usage() - raise SystemExit(1) +def build_parser(): + parser = argparse.ArgumentParser(prog=PROG) + subparsers = parser.add_subparsers(dest="command", required=True) - command = sys.argv[1] - handler = COMMANDS.get(command) - if handler is None: - usage() - raise SystemExit(1) + p = subparsers.add_parser("set_admin_password") + p.add_argument("initial_password") + p.add_argument("password") + p.set_defaults(func=cmd_set_admin_password) + + p = subparsers.add_parser("create_puzzle") + p.add_argument("id") + p.add_argument("solution") + p.add_argument("password") + p.set_defaults(func=cmd_create_puzzle) + + p = subparsers.add_parser("join") + p.add_argument("username") + p.set_defaults(func=cmd_join) + + p = subparsers.add_parser("auth_state") + p.add_argument("username") + p.set_defaults(func=cmd_auth_state) - handler(sys.argv[2:]) + p = subparsers.add_parser("submit") + p.add_argument("username") + p.add_argument("id") + p.add_argument("solution") + p.set_defaults(func=cmd_submit) + + p = subparsers.add_parser("logout") + p.add_argument("username") + p.set_defaults(func=cmd_logout) + + p = subparsers.add_parser("mock_puzzles") + p.add_argument("from_id_int", type=int) + p.add_argument("to_id_int", type=int) + p.add_argument("password") + p.set_defaults(func=cmd_mock_puzzles) + + p = subparsers.add_parser("mock_join") + p.add_argument("from_char", type=lower_char) + p.add_argument("to_char", type=lower_char) + p.set_defaults(func=cmd_mock_join) + + return parser + + +def main(): + parser = build_parser() + args = parser.parse_args() + args.func(args) if __name__ == "__main__":