-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathgit-set-attr-type.py
More file actions
executable file
·139 lines (125 loc) · 4.87 KB
/
git-set-attr-type.py
File metadata and controls
executable file
·139 lines (125 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "click",
# "utz",
# ]
# ///
#
# Manage "attr type" for one or more file extensions.
#
# Modify, remove, comment, or uncomment `.gitattributes` lines pertaining to filename-glob patterns:
#
# ```bash
# # *.ipynb diff=ipynb
# git set-attr-type.py -a diff ipynb # a.k.a. `gsdt ipynb`
#
# # *.ipynb merge=nb
# git set-attr-type.py -a merge ipynb nb # a.k.a. `gsmt ipynb nb`
#
# # *.ipynb diff=nb
# # *.ipynb merge=nb
# git set-attr-type.py -a diff,merge ipynb nb # a.k.a. `gsdm ipynb nb`
#
# # "Unset" `diff` and `merge` keys for `*.ipynb` files:
# git set-attr-type.py -a diff,merge -u ipynb # a.k.a. `gudm ipynb`
# ```
#
# Passing `-c/--comment-lines` will comment/uncomment lines matching the given blob, instead of modifying/removing in-place.
import re
from os import rename
from os.path import realpath, dirname, basename
from tempfile import NamedTemporaryFile
from click import command, argument, option
from utz import proc, err
@command
@option(
'-a', '--attr', 'attrs', required=True,
callback=lambda ctx, param, value: None if value is None else value.split(','),
help='Attr-types to manipulate (e.g. `diff`, `merge`; comma-delimited)'
)
@option('-c', '--comment-lines', is_flag=True, help='Comment/Uncomment lines (instead of removing/modifying)')
@option('-u', '--unset', is_flag=True, help='')
@argument('extension')
@argument('name', required=False)
def main(
attrs: tuple[str, ...] | None,
comment_lines: bool,
unset: bool,
extension: str,
name: str | None,
):
"""Manage "attr type" for one or more file extensions."""
if not attrs:
err(f"Pass one or more (comma-delimited) attr-types as `-a/--attr`")
if unset:
if name:
raise ValueError(f"Pass -u/--unset xor <name>")
elif not name:
name = extension
attrs_file = proc.line('git', 'config', 'core.attributesfile')
attrs_dir = dirname(realpath(attrs_file))
with open(attrs_file, 'r') as f:
lines = [ line.rstrip('\n') for line in f.readlines() ]
attrs_group = r'(?P<attr>%s)' % '|'.join(attrs)
rgx = re.compile(r'(?P<comment>#\s*)?(?P<pattern>\*\.%s)\s+%s=(?P<name>\w+)(?P<suffix>.*)' % (extension, attrs_group))
with NamedTemporaryFile(dir=attrs_dir, prefix=basename(attrs_file), delete=False) as tmp_file:
tmp_path = tmp_file.name
with open(tmp_path, 'w') as f:
founds = set()
for idx, line in enumerate(lines):
lineno = idx + 1
def log(msg: str):
err(f"{lineno}: {msg}")
m = rgx.fullmatch(line)
if m:
attr = m['attr']
found = attr in founds
comment = m['comment']
if name and m['name'] == name:
if comment:
if found:
if comment_lines:
log(f"leaving extra match commented: {line}")
else:
log(f"removing extra match commented: {line}")
continue
else:
log(f"uncommenting line: {line}")
line = line[len(comment):]
founds.add(attr)
else:
if found:
if comment_lines:
log(f"commenting extra match: {line}")
else:
log(f"removing extra match: {line}")
continue
else:
log(f"found line: {line}")
founds.add(attr)
else:
if comment:
if comment_lines:
log(f"leaving line commented: {line}")
else:
log(f"removing commented line: {line}")
continue
else:
if comment_lines:
log(f"commenting line: {line}")
line = f'# {line}'
else:
log(f"removing line: {line}")
continue
print(line, file=f)
if name:
for attr in attrs:
if attr not in founds:
line = f"*.{extension} {attr}={name}"
err(f"Appending line: {line}")
print(line, file=f)
rename(tmp_path, attrs_file)
if __name__ == "__main__":
main()