main
 1#!/usr/bin/env python3
 2"""List git repositories under ~/src/ for raffi script filter.
 3
 4Outputs Alfred Script Filter JSON format.
 5Results are cached for 2 hours.
 6Usage: lazyworktree-raffi [query]
 7"""
 8
 9import json
10import os
11import sys
12import tempfile
13import time
14
15CACHE_MAX_AGE = 7200  # 2 hours
16SRC_DIR = os.path.expanduser("~/src")
17
18
19def cache_paths():
20    cache_home = os.environ.get("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
21    cache_dir = os.path.join(cache_home, "lazyworktree-raffi")
22    os.makedirs(cache_dir, exist_ok=True)
23    return cache_dir, os.path.join(cache_dir, "repos.json")
24
25
26def needs_rebuild(cache_file):
27    try:
28        age = time.time() - os.path.getmtime(cache_file)
29        return age > CACHE_MAX_AGE
30    except FileNotFoundError:
31        return True
32
33
34def find_repos():
35    repos = []
36    for root, dirs, _files in os.walk(SRC_DIR):
37        # Skip worktree internal directories
38        if "worktrees" in root.split(os.sep):
39            dirs.clear()
40            continue
41        if ".git" in dirs or ".git" in _files:
42            repos.append(root)
43            dirs.clear()  # Don't descend into nested repos
44    repos.sort()
45    return [
46        {
47            "title": os.path.relpath(r, SRC_DIR),
48            "subtitle": r,
49            "arg": r,
50        }
51        for r in repos
52    ]
53
54
55def rebuild_cache(cache_dir, cache_file):
56    repos = find_repos()
57    fd, tmpfile = tempfile.mkstemp(suffix=".json", dir=cache_dir)
58    try:
59        with os.fdopen(fd, "w") as f:
60            json.dump(repos, f)
61        os.replace(tmpfile, cache_file)
62    except BaseException:
63        os.unlink(tmpfile)
64        raise
65
66
67def load_cache(cache_file):
68    try:
69        with open(cache_file) as f:
70            data = f.read()
71            if not data:
72                return []
73            return json.loads(data)
74    except (FileNotFoundError, json.JSONDecodeError):
75        return []
76
77
78def main():
79    cache_dir, cache_file = cache_paths()
80
81    if needs_rebuild(cache_file):
82        rebuild_cache(cache_dir, cache_file)
83
84    items = load_cache(cache_file)
85
86    query = " ".join(sys.argv[1:]).strip().lower()
87    if query:
88        items = [i for i in items if query in i["title"].lower()]
89
90    json.dump({"items": items}, sys.stdout)
91
92
93if __name__ == "__main__":
94    main()