#!/usr/bin/env python3 import shlex import subprocess import sys from shutil import which from pathlib import Path def is_tool(name): """Check whether `name` is on PATH and marked as executable.""" if not which(name): print(f"ERROR: {name} is not found in your PATH") sys.exit(1) def is_repo(): if not (Path.cwd() / ".git").is_dir(): print(color("Not a git repo.","9")) print("exiting...") sys.exit(1) def run(cmd, capture=True, returncode=False): result = subprocess.run( shlex.split(cmd), stdout=subprocess.PIPE if capture else None, text=True, shell=False, ) if returncode: return result.returncode elif capture: return result.stdout.strip() def confirm(q): return run( "gum confirm " "--selected.bold " "--selected.background 11 " "--selected.foreground 8 " f"'{q}'", returncode=True, ) # TODO: make gum (and git?) a class an obsufcate the commands def color(text, color): return run(f"gum style --foreground {color} '{text}'") def nl_to_ol(text): return " ".join([f"'{f}'" for f in text.splitlines()]) def check_status(): files = nl_to_ol(run("git ls-files -dmo")) if not files: print(color("nothing to stage", "red")) sys.exit(1) else: return files def stage_files(files): to_add = run(f"gum choose --no-limit {files} ") run(f"git add {to_add}") def get_staged_info(): staged = run("git diff --cached --stat") fstaged = [] if not staged: files = check_status() print(color("nothing staged..", 9)) if files and confirm("Would you like to stage files?") == 0: stage_files(files) return get_staged_info() else: print("exiting...") sys.exit(0) for text in staged.splitlines()[-1].split(","): if "changed" in text: fstaged.append(color(text.strip(), 11)) elif "insertion" in text: fstaged.append(color(text.strip(), 10)) elif "deletion" in text: fstaged.append(color(text.strip(), 9)) else: print(f"Unexpected output from git diff: {text}") return "\n\t".join(fstaged) def main(): is_tool("gum") is_tool("git") is_repo() run( "gum style " "--foreground 212 --border-foreground 212 --border rounded " '--align center --width 30 --margin "1 4" ' f"'{color('ConCom',11)}'", capture=False, ) print(f" staged: \n\t{get_staged_info()}\n") commit_type = run( "gum choose fix feat docs style refactor test chore revert custom" ) if not commit_type: print("exiting...") sys.exit(1) elif commit_type == "custom": commit_type = run('gum input --placeholder "type"') scope = run('gum input --placeholder "scope"') scope = f"({scope})" if scope else "" summary = run( f'gum input --value "{commit_type}{scope}: " --placeholder "Summary of this change"' ) print(f"{color('message:',11)}\n {summary}") actions = { "commit": lambda: run(f"git commit -m '{summary}'", capture=False), "commit(with message)": lambda: run( f"git commit -m '{summary}' -m '# note: abort with :cq!' -e", capture=False ), "quit": lambda: (print("exiting..."), sys.exit()), } print("\nHow to proceed?\n") choices = " ".join([f"'{choice}'" for choice in actions]) choice = str(run(f"gum choose {choices}")) actions[choice]() if __name__ == "__main__": main()