-
-
Save AdamGagorik/fdcf21aa486a31e38ccdee8868ff9220 to your computer and use it in GitHub Desktop.
Interactive gh gist callback
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
EXTRAS | |
path: Get path of local clone | |
link: Register a gist locally | |
sync: Sync all registered gists | |
debug: Debug script configuration | |
status: Get status of registered gists | |
select: Select a gist using fuzzy finder | |
attach: Attach a gist to the local registry | |
detach: Detach a gist from the local registry | |
modify: Modify gist selected via fuzzy finder | |
install: Bootstrap install this script locally | |
uninstall: Instructions for removing this script | |
NOTES | |
To attach a gist you do not personally own, use the full URL | |
gist attach https://gist.github.com/RichardBronosky/56d8f614fab2bacdd8b048fb58d0c0c7 | |
""" | |
import io | |
import os | |
import shutil | |
from argparse import ArgumentParser | |
from pathlib import Path | |
from subprocess import PIPE, CompletedProcess, Popen, run | |
from typing import Any, Generator, Iterable | |
GIST_ID_PREFIX = "https://gist.github.com/" | |
THIS_GIST_HASH = "AdamGagorik/fdcf21aa486a31e38ccdee8868ff9220" | |
LOCAL_GIST_DIR = Path(os.environ.get("LOCAL_GIST_DIR", os.path.expanduser("~/.gists"))) | |
LOCAL_GIST_BIN = Path(os.environ.get("LOCAL_GIST_BIN", LOCAL_GIST_DIR.joinpath("bin"))) | |
GIT_EXECUTABLE = os.environ.get("GIT_EXECUTABLE", "git") | |
def args() -> dict[str, Any]: | |
parser = ArgumentParser(description="wrapper around gh gist", add_help=False) | |
parser.add_argument( | |
"action", | |
choices=[ | |
"clone", | |
"create", | |
"delete", | |
"edit", | |
"list", | |
"rename", | |
"view", | |
# extras | |
"path", | |
"link", | |
"sync", | |
"debug", | |
"status", | |
"select", | |
"attach", | |
"detach", | |
"modify", | |
"install", | |
"uninstall", | |
], | |
nargs="?", | |
) | |
opts, remaining = parser.parse_known_args() | |
opts.extras = remaining | |
return opts.__dict__ | |
def main(action: str | None, extras: Iterable[str] = ()) -> None: | |
match action: | |
case None: | |
default_help() | |
case "path": | |
gist = get_local_gist(*extras) | |
print(gist) | |
case "link": | |
assert not extras | |
link_all_gists() | |
case "sync": | |
assert not extras | |
sync_all_gists() | |
case "debug": | |
assert not extras | |
debug_all_gists() | |
case "status": | |
assert not extras | |
get_gist_status() | |
case "select": | |
g_id = get_g_id(*extras) | |
print(g_id) | |
case "attach": | |
g_id = get_g_id(*extras) | |
gist = clone_gist_local(g_id) | |
link_gist(gist) | |
case "detach": | |
gist = get_local_gist(*extras) | |
detach_local_gist(gist) | |
case "modify": | |
g_id = get_g_id(*extras) | |
if run_default_command("edit", g_id, *extras).returncode == 0: | |
sync_gist(gist=LOCAL_GIST_DIR.joinpath(g_id)) | |
case "install": | |
run_bootstrap_install() | |
case "uninstall": | |
run_bootstrap_uninstall() | |
case _: | |
if run_default_command(action, *extras).returncode != 0: | |
raise SystemExit("Error running gh gist") | |
def agree(name: Any, c: str = "?", after: str = "") -> bool: | |
return input( | |
"{} {} [y/n] ".format(" {} ".format(name).center(80, c), after) | |
).lower() in ("y", "yes") | |
def title(name: Any, c: str = "-", after: str = "") -> None: | |
print("{} {}".format(" {} ".format(name).center(80, c), after)) | |
def debug(*msg: str, **kwargs: Any) -> None: | |
print("".join(msg).format(**kwargs)) | |
def default_help() -> None: | |
run(["gh", "gist", "--help"]) | |
print(__doc__[1:-1]) | |
def debug_all_gists() -> None: | |
title("REGISTERED") | |
run(["tree", LOCAL_GIST_DIR]) | |
title("CONFIGURATION") | |
print("GIT_EXECUTABLE: {}".format(GIT_EXECUTABLE)) | |
print("THIS_GIST_HASH: {}".format(THIS_GIST_HASH)) | |
print("GIST_ID_PREFIX: {}".format(GIST_ID_PREFIX)) | |
print("LOCAL_GIST_DIR: {}".format(LOCAL_GIST_DIR)) | |
print("LOCAL_GIST_BIN: {}".format(LOCAL_GIST_BIN)) | |
def get_local_path() -> str: | |
pass | |
def link_gist(gist: Path) -> None: | |
LOCAL_GIST_BIN.mkdir(exist_ok=True, parents=True) | |
for src in gist.iterdir(): | |
if src.is_file(): | |
if src.suffix.lower() not in (".md", ".txt"): | |
title(src.parent.name, "*", src.name) | |
dst = LOCAL_GIST_BIN.joinpath(src.name) | |
if dst.exists(): | |
dst.unlink() | |
dst.symlink_to(src) | |
run(["chmod", "+x", dst]) | |
def unlink_gist(gist: Path) -> None: | |
for src in gist.iterdir(): | |
if src.is_file(): | |
dst = LOCAL_GIST_BIN.joinpath(src.name) | |
if dst.exists(): | |
title(src.parent, "*", src.name) | |
dst.unlink() | |
def walk_all_gist() -> Generator[Path, None, None]: | |
for root, dirs, files in os.walk(LOCAL_GIST_DIR): | |
if Path(root).name == ".git": | |
dirs[:] = [] | |
else: | |
for basename in dirs: | |
path = Path(root).joinpath(basename) | |
if path.is_dir() and path.joinpath(".git").exists(): | |
yield path | |
def link_all_gists() -> None: | |
title("LINK") | |
for gist in walk_all_gist(): | |
link_gist(gist) | |
def sync_gist(gist: Path) -> None: | |
if not gist.exists(): | |
title(gist, "*", "skipped, gist is not cloned locally") | |
return | |
title(gist.parent.name, "*") | |
p = run( | |
[GIT_EXECUTABLE, "-C", gist, "reset", "--hard"], capture_output=True, check=True | |
) | |
print(p.stdout.decode("utf-8", errors="ignore").rstrip()) | |
p = run([GIT_EXECUTABLE, "-C", gist, "pull"], capture_output=True, check=True) | |
print(p.stdout.decode("utf-8", errors="ignore").rstrip()) | |
def sync_all_gists() -> None: | |
title("SYNC") | |
for gist in walk_all_gist(): | |
sync_gist(gist) | |
link_gist(gist) | |
def get_gist_status() -> None: | |
for gist in walk_all_gist(): | |
title(gist, "*") | |
p = run([GIT_EXECUTABLE, "-C", gist, "status"], capture_output=True, check=True) | |
print(p.stdout.decode("utf-8", errors="ignore").rstrip()) | |
def clone_gist_local(g_id: str) -> Path: | |
if g_id.startswith(GIST_ID_PREFIX): | |
prefix, _, suffix = g_id.rpartition(GIST_ID_PREFIX) | |
gist = LOCAL_GIST_DIR.joinpath(suffix) | |
g_id = suffix | |
else: | |
gist = LOCAL_GIST_DIR.joinpath(g_id) | |
if gist.exists(): | |
sync_gist(gist) | |
return gist | |
else: | |
gist.mkdir(parents=True, exist_ok=True) | |
run([GIT_EXECUTABLE, "clone", f"{GIST_ID_PREFIX}/{g_id}", gist]) | |
return gist | |
def detach_local_gist(gist: Path) -> None: | |
if not gist.exists(): | |
title(gist, "*", "skipped, gist is not cloned locally") | |
return | |
if not gist.joinpath(".git").exists(): | |
title(gist, "*", "skipped, gist is not a git repository") | |
return | |
if not gist.is_relative_to(LOCAL_GIST_DIR): | |
title(gist, "*", "skipped, gist is not inside LOCAL_GIST_DIR") | |
return | |
if agree(gist, "?", "Remove gist from local registry?"): | |
unlink_gist(gist) | |
shutil.rmtree(gist) | |
title(gist, "*", "deleted") | |
else: | |
title(gist, "*", "skipped") | |
def get_origin_url(gist: Path) -> str: | |
p = run( | |
[GIT_EXECUTABLE, "-C", f"{gist}", "remote", "get-url", "origin"], | |
capture_output=True, | |
check=True, | |
) | |
return p.stdout.decode("utf-8", errors="ignore").strip() | |
def set_origin_url(gist: Path, url: str) -> None: | |
run([GIT_EXECUTABLE, "-C", f"{gist}", "remote", "set-url", "origin", url]) | |
def ssh_to_https_url(url: str) -> str: | |
return url.replace(r"https://", "git@").replace(".com/", ".com:") | |
def run_default_command(action: str, *extras: str) -> CompletedProcess: | |
return run(["gh", "gist", action, *extras]) | |
def get_g_id(query: str = None) -> str: | |
if query is not None and query.startswith(GIST_ID_PREFIX): | |
return query | |
p1 = Popen(["gh", "gist", "list", "--limit", "256"], stdout=PIPE) | |
p2 = Popen(["column", "-s$\t", "-t"], stdin=p1.stdout, stdout=PIPE) | |
p3 = run( | |
[ | |
"fzf", | |
"--preview=gh gist view {1}", | |
"--preview-label=[ gist ]", | |
"--preview-window=50%,border-double,bottom", | |
*(("--query", query) if query else ()), | |
], | |
stdin=p2.stdout, | |
stdout=PIPE, | |
) | |
if p3.returncode != 0: | |
raise SystemExit("No gist selected") | |
else: | |
return p3.stdout.decode("utf-8").split(" ")[0] | |
def get_local_gist(*query: str) -> Path: | |
stream = io.StringIO() | |
for gist in walk_all_gist(): | |
members = [p for p in gist.iterdir() if p.is_file()] | |
stream.write( | |
f"{str(gist.relative_to(LOCAL_GIST_DIR)):32} {', '.join([p.name for p in members])}\n" | |
) | |
p3 = Popen( | |
[ | |
"fzf", | |
f"--preview=cat {LOCAL_GIST_DIR.joinpath('{1}', '{2}')}", | |
"--preview-label=[ gist ]", | |
"--preview-window=50%,border-double,bottom", | |
*(("--query", " ".join(query)) if query else ()), | |
], | |
stdin=PIPE, | |
stdout=PIPE, | |
) | |
stdout, stderr = p3.communicate(input=stream.getvalue().encode("utf-8")) | |
if p3.returncode != 0: | |
raise SystemExit("No gist selected") | |
else: | |
g_id = stdout.decode("utf-8").split(" ")[0].strip() | |
return LOCAL_GIST_DIR.joinpath(g_id) | |
def get_gist_name(g_id: str) -> str: | |
return ( | |
run(["gh", "api", f"gists/{g_id}", "--jq", ".files | keys[]"]) | |
.stdout.decode("utf-8") | |
.strip() | |
) | |
def run_bootstrap_install() -> None: | |
gist = clone_gist_local(THIS_GIST_HASH) | |
link_gist(gist) | |
def run_bootstrap_uninstall() -> None: | |
print("Remove the following directories:") | |
print("LOCAL_GIST_DIR: {}".format(LOCAL_GIST_DIR)) | |
print("LOCAL_GIST_BIN: {}".format(LOCAL_GIST_BIN)) | |
print("Remove LOCAL_GIST_BIN from your PATH") | |
if __name__ == "__main__": | |
main(**args()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requires
python3.12
fzf
git
gh
Setup
Bootstrap install it once with curl.
Put the directory you ran it from on your path.
Usage
Mostly it is a wrapper around
gh gist
command. Run gist --help to see the options.Extras