1#!/usr/bin/env nix-shell
2#!nix-shell -i python3 -p "python3.withPackages(ps: with ps; [ ])" nix
3"""
4A program to remove old aliases or convert old aliases to throws
5Example usage:
6./maintainers/scripts/remove-old-aliases.py --year 2018 --file ./pkgs/top-level/aliases.nix
7
8Check this file with mypy after every change!
9$ mypy --strict maintainers/scripts/remove-old-aliases.py
10"""
11import argparse
12import shutil
13import subprocess
14from datetime import date as datetimedate
15from datetime import datetime
16from pathlib import Path
17
18
19def process_args() -> argparse.Namespace:
20 """process args"""
21 arg_parser = argparse.ArgumentParser()
22 arg_parser.add_argument(
23 "--year", required=True, type=int, help="operate on aliases older than $year"
24 )
25 arg_parser.add_argument(
26 "--month",
27 type=int,
28 default=1,
29 help="operate on aliases older than $year-$month",
30 )
31 arg_parser.add_argument(
32 "--only-throws",
33 action="store_true",
34 help="only operate on throws. e.g remove throws older than $date",
35 )
36 arg_parser.add_argument("--file", required=True, type=Path, help="alias file")
37 arg_parser.add_argument(
38 "--dry-run", action="store_true", help="don't modify files, only print results"
39 )
40 return arg_parser.parse_args()
41
42
43def get_date_lists(
44 txt: list[str], cutoffdate: datetimedate, only_throws: bool
45) -> tuple[list[str], list[str], list[str]]:
46 """get a list of lines in which the date is older than $cutoffdate"""
47 date_older_list: list[str] = []
48 date_older_throw_list: list[str] = []
49 date_sep_line_list: list[str] = []
50
51 for lineno, line in enumerate(txt, start=1):
52 line = line.rstrip()
53 my_date = None
54 for string in line.split():
55 string = string.strip(":")
56 try:
57 # strip ':' incase there is a string like 2019-11-01:
58 my_date = datetime.strptime(string, "%Y-%m-%d").date()
59 except ValueError:
60 try:
61 my_date = datetime.strptime(string, "%Y-%m").date()
62 except ValueError:
63 continue
64
65 if (
66 my_date is None
67 or my_date > cutoffdate
68 or "preserve, reason:" in line.lower()
69 ):
70 continue
71
72 if "=" not in line:
73 date_sep_line_list.append(f"{lineno} {line}")
74 # 'if' lines could be complicated
75 elif "if " in line and "if =" not in line:
76 print(f"RESOLVE MANUALLY {line}")
77 elif "throw" in line:
78 date_older_throw_list.append(line)
79 elif not only_throws:
80 date_older_list.append(line)
81
82 return (
83 date_older_list,
84 date_sep_line_list,
85 date_older_throw_list,
86 )
87
88
89def convert_to_throw(date_older_list: list[str]) -> list[tuple[str, str]]:
90 """convert a list of lines to throws"""
91 converted_list = []
92 for line in date_older_list.copy():
93 indent: str = " " * (len(line) - len(line.lstrip()))
94 before_equal = ""
95 after_equal = ""
96 try:
97 before_equal, after_equal = (x.strip() for x in line.split("=", maxsplit=2))
98 except ValueError as err:
99 print(err, line, "\n")
100 date_older_list.remove(line)
101 continue
102
103 alias = before_equal
104 alias_unquoted = before_equal.strip('"')
105 replacement = next(x.strip(";:") for x in after_equal.split())
106 replacement = replacement.removeprefix("pkgs.")
107
108 converted = (
109 f"{indent}{alias} = throw \"'{alias_unquoted}' has been"
110 f" renamed to/replaced by '{replacement}'\";"
111 f" # Converted to throw {datetime.today().strftime('%Y-%m-%d')}"
112 )
113 converted_list.append((line, converted))
114
115 return converted_list
116
117
118def generate_text_to_write(
119 txt: list[str],
120 date_older_list: list[str],
121 converted_to_throw: list[tuple[str, str]],
122 date_older_throw_list: list[str],
123) -> list[str]:
124 """generate a list of text to be written to the aliasfile"""
125 text_to_write: list[str] = []
126 for line in txt:
127 text_to_append: str = ""
128 if converted_to_throw:
129 for tupl in converted_to_throw:
130 if line == tupl[0]:
131 text_to_append = f"{tupl[1]}\n"
132 if line not in date_older_list and line not in date_older_throw_list:
133 text_to_append = f"{line}\n"
134 if text_to_append:
135 text_to_write.append(text_to_append)
136
137 return text_to_write
138
139
140def write_file(
141 aliasfile: Path,
142 text_to_write: list[str],
143) -> None:
144 """write file"""
145 temp_aliasfile = Path(f"{aliasfile}.raliases")
146 with open(temp_aliasfile, "w", encoding="utf-8") as far:
147 for line in text_to_write:
148 far.write(line)
149 print("\nChecking the syntax of the new aliasfile")
150 try:
151 subprocess.run(
152 ["nix-instantiate", "--eval", temp_aliasfile],
153 check=True,
154 stdout=subprocess.DEVNULL,
155 )
156 except subprocess.CalledProcessError:
157 print(
158 "\nSyntax check failed,",
159 "there may have been a line which only has\n"
160 'aliasname = "reason why";\n'
161 "when it should have been\n"
162 'aliasname = throw "reason why";',
163 )
164 temp_aliasfile.unlink()
165 return
166 shutil.move(f"{aliasfile}.raliases", aliasfile)
167 print(f"{aliasfile} modified! please verify with 'git diff'.")
168
169
170def main() -> None:
171 """main"""
172 args = process_args()
173
174 only_throws = args.only_throws
175 aliasfile = Path(args.file).absolute()
176 cutoffdate = (datetime.strptime(f"{args.year}-{args.month}-01", "%Y-%m-%d")).date()
177
178 txt: list[str] = (aliasfile.read_text(encoding="utf-8")).splitlines()
179
180 date_older_list: list[str] = []
181 date_sep_line_list: list[str] = []
182 date_older_throw_list: list[str] = []
183
184 date_older_list, date_sep_line_list, date_older_throw_list = get_date_lists(
185 txt, cutoffdate, only_throws
186 )
187
188 converted_to_throw: list[tuple[str, str]] = []
189 if date_older_list:
190 converted_to_throw = convert_to_throw(date_older_list)
191 print(" Will be converted to throws. ".center(100, "-"))
192 for l_n in date_older_list:
193 print(l_n)
194
195 if date_older_throw_list:
196 print(" Will be removed. ".center(100, "-"))
197 for l_n in date_older_throw_list:
198 print(l_n)
199
200 if date_sep_line_list:
201 print(" On separate line, resolve manually. ".center(100, "-"))
202 for l_n in date_sep_line_list:
203 print(l_n)
204
205 if not args.dry_run:
206 text_to_write = generate_text_to_write(
207 txt, date_older_list, converted_to_throw, date_older_throw_list
208 )
209 write_file(aliasfile, text_to_write)
210
211
212if __name__ == "__main__":
213 main()