1
|
|
|
#!/usr/bin/env python3 |
2
|
|
|
# -*- coding: utf-8 -*- |
3
|
|
|
"""Select a gitignore for the current directory""" |
4
|
|
|
from pathlib import Path |
5
|
|
|
from shutil import copy |
6
|
|
|
import subprocess |
7
|
|
|
import sys |
8
|
|
|
from tempfile import NamedTemporaryFile |
9
|
|
|
|
10
|
|
|
try: |
11
|
|
|
from appdirs import user_data_dir |
12
|
|
|
|
13
|
|
|
GI_DIR = Path(user_data_dir("gitignore")) |
14
|
|
|
except ImportError: |
15
|
|
|
from os import getenv |
16
|
|
|
|
17
|
|
|
GI_DIR = Path(f"{getenv('HOME')}/templates/gitignore") |
18
|
|
|
|
19
|
|
|
|
20
|
|
|
def dir_yield(path: Path): |
21
|
|
|
"""recursively yield from all directories in the gitignore repo""" |
22
|
|
|
for entry in path.iterdir(): |
23
|
|
|
if entry.is_dir() and "git" not in entry.name: |
24
|
|
|
yield from dir_yield(entry.resolve()) |
25
|
|
|
elif not entry.name.endswith(".gitignore"): |
26
|
|
|
continue |
27
|
|
|
yield entry.resolve() |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
def get_gitignores(): |
31
|
|
|
"""download the gitignore repository if it doesn't exist""" |
32
|
|
|
try: |
33
|
|
|
# HACK: this checks if the dir: doesn't exist OR exists and is empty |
34
|
|
|
if not GI_DIR.exists() or (not GI_DIR.exists() and not any(GI_DIR.iterdir())): |
35
|
|
|
GI_DIR.mkdir(parents=True) |
36
|
|
|
print("Cloning gitignore repository...") |
37
|
|
|
subprocess.run( |
38
|
|
|
["git", "clone", "https://github.com/github/gitignore", GI_DIR], |
39
|
|
|
check=True, |
40
|
|
|
) |
41
|
|
|
print(f"gitignores cloned to '{GI_DIR}'") |
42
|
|
|
return |
43
|
|
|
except PermissionError as err: |
44
|
|
|
raise NotImplementedError from err |
45
|
|
|
except subprocess.CalledProcessError as err: |
46
|
|
|
raise NotImplementedError from err |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
def select_gitignore(options): |
50
|
|
|
"""interactively select a gitignore with fzf""" |
51
|
|
|
chosen = "" |
52
|
|
|
try: |
53
|
|
|
with NamedTemporaryFile(mode="w+") as tmpfile: |
54
|
|
|
# add options to file to pipe into fzf |
55
|
|
|
with open(tmpfile.name, "w+", encoding="utf-8") as tmp: |
56
|
|
|
for lang in options: |
57
|
|
|
tmp.write(lang + "\n") |
58
|
|
|
tmp.seek(0) |
59
|
|
|
# decode the output to a string and strip the newline |
60
|
|
|
chosen = ( |
61
|
|
|
subprocess.check_output( |
62
|
|
|
f"cat {tmpfile.name} | fzf +m -i", shell=True |
63
|
|
|
) |
64
|
|
|
.decode(sys.stdout.encoding) |
65
|
|
|
.strip() |
66
|
|
|
) |
67
|
|
|
except PermissionError as err: |
68
|
|
|
raise NotImplementedError from err |
69
|
|
|
except subprocess.CalledProcessError as err: |
70
|
|
|
if not chosen: |
71
|
|
|
sys.exit() |
72
|
|
|
raise NotImplementedError from err |
73
|
|
|
return chosen |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
def main(): |
77
|
|
|
"""select a gitignore""" |
78
|
|
|
# dict comprehension where key is the filename w/o gitignore and value is file path |
79
|
|
|
get_gitignores() |
80
|
|
|
gitignores = { |
81
|
|
|
f.name.replace(".gitignore", ""): f.resolve() for f in dir_yield(GI_DIR) |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
# use argument if one is provided, otherwise choose interactively |
85
|
|
|
try: |
86
|
|
|
chosen = sys.argv[1] |
87
|
|
|
except IndexError: |
88
|
|
|
chosen = select_gitignore(gitignores).casefold() |
89
|
|
|
|
90
|
|
|
try: |
91
|
|
|
# change all dict keys to lowercase |
92
|
|
|
gitignores = {name.lower(): loc for name, loc in gitignores.items()} |
93
|
|
|
copy(gitignores[chosen], "./.gitignore") |
94
|
|
|
except KeyError: |
95
|
|
|
print(f"'{chosen}' is not a valid gitignore type.") |
96
|
|
|
sys.exit(1) |
97
|
|
|
|
98
|
|
|
|
99
|
|
|
if __name__ == "__main__": |
100
|
|
|
main() |
101
|
|
|
|