runv-who (4126B)
1 #!/usr/bin/env python3 2 """Lista membros da runv.club.""" 3 4 from __future__ import annotations 5 6 import argparse 7 import json 8 import sys 9 from pathlib import Path 10 from typing import Any 11 12 sys.tracebacklimit = 0 13 14 15 def _bootstrap() -> None: 16 from pathlib import Path 17 18 installed = Path("/usr/local/share/runv/lib") 19 candidates = [installed] 20 script = Path(__file__).resolve() 21 if script.parent.name == "bin": 22 candidates.insert(0, script.parent.parent / "lib") 23 for c in candidates: 24 if (c / "runv_community.py").is_file() and str(c) not in sys.path: 25 sys.path.insert(0, str(c)) 26 return 27 28 29 _bootstrap() 30 import runv_community as rc # noqa: E402 31 32 33 def collect_member(username: str) -> dict[str, Any]: 34 paths = rc.home_paths(username) 35 try: 36 has_homepage = rc.path_is_file(paths["public_index"]) 37 homepage_mtime = ( 38 rc.homepage_mtime_iso(paths["public_index"]) if has_homepage else None 39 ) 40 except OSError: 41 has_homepage = False 42 homepage_mtime = None 43 return { 44 "username": username, 45 "homepage": f"/~{username}/", 46 "has_homepage": has_homepage, 47 "homepage_mtime": homepage_mtime, 48 "has_plan": rc.file_has_content(paths["plan"]), 49 "has_project": rc.file_has_content(paths["project"]), 50 } 51 52 53 def sort_members(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: 54 with_home = [r for r in rows if r["has_homepage"]] 55 without_home = [r for r in rows if not r["has_homepage"]] 56 57 def mtime_key(r: dict[str, Any]) -> str: 58 return r.get("homepage_mtime") or "" 59 60 with_home.sort(key=lambda r: mtime_key(r), reverse=True) 61 without_home.sort(key=lambda r: r["username"].lower()) 62 return with_home + without_home 63 64 65 def filter_active(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: 66 return [ 67 r 68 for r in rows 69 if r["has_homepage"] or r["has_plan"] or r["has_project"] 70 ] 71 72 73 def print_text_table(rows: list[dict[str, Any]]) -> None: 74 print("Membros runv.club\n") 75 if not rows: 76 print("(nenhum membro encontrado)") 77 return 78 for r in rows: 79 user = r["username"] 80 if r["has_homepage"]: 81 hm = rc.format_mtime_local(r.get("homepage_mtime")) or "?" 82 home_col = f"home {hm}" 83 else: 84 home_col = "sem homepage" 85 flags = [] 86 if r["has_plan"]: 87 flags.append(".plan") 88 if r["has_project"]: 89 flags.append(".project") 90 flag_col = " ".join(flags) if flags else "-" 91 print(f"{user:<12} {home_col:<22} {flag_col:<14} {r['homepage']}") 92 93 94 def main(argv: list[str] | None = None) -> int: 95 try: 96 p = argparse.ArgumentParser( 97 prog="runv-who", 98 description="Lista membros da runv.club.", 99 ) 100 p.add_argument("--json", action="store_true", help="saída em JSON") 101 p.add_argument("--limit", type=int, default=None, metavar="N", help="limitar resultados") 102 p.add_argument( 103 "--active", 104 action="store_true", 105 help="só utilizadores com homepage, .plan ou .project", 106 ) 107 args = p.parse_args(argv) 108 109 if args.limit is not None and args.limit < 1: 110 rc.friendly_exit("--limit deve ser um inteiro positivo.") 111 112 names, warning = rc.load_member_usernames(rc.DEFAULT_USERS_JSON, rc.DEFAULT_HOME_ROOT) 113 if warning: 114 print(warning, file=sys.stderr) 115 116 rows = [collect_member(u) for u in names] 117 if args.active: 118 rows = filter_active(rows) 119 rows = sort_members(rows) 120 if args.limit is not None: 121 rows = rows[: args.limit] 122 123 if args.json: 124 print(json.dumps(rows, ensure_ascii=False, indent=2)) 125 return 0 126 127 print_text_table(rows) 128 return 0 129 except KeyboardInterrupt: 130 print("\nInterrompido.", file=sys.stderr) 131 return 130 132 except SystemExit: 133 raise 134 except Exception as e: 135 rc.friendly_exit(f"erro: {e}") 136 137 138 if __name__ == "__main__": 139 raise SystemExit(main())