Passed
Push — main ( fa2423...891932 )
by J
01:24
created

gitignore.select_gitignore()   B

Complexity

Conditions 7

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 25
rs 8
c 0
b 0
f 0
cc 7
nop 1
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