nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1#!/usr/bin/env nix-shell
2#!nix-shell -i python3 -p "python3.withPackages(ps: with ps; [ ])" nix
3"""
4Converts old aliases to warnings, converts old warnings to throws, and removes old 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="Deprecated, use --only throws instead",
35 )
36 arg_parser.add_argument(
37 "--only",
38 choices=["aliases", "warnings", "throws"],
39 help="Only act on the specified types"
40 "(i.e. only act on entries that are 'normal' aliases, warnings, or throws)."
41 "Can be repeated.",
42 action="append",
43 dest="operate_on",
44 )
45 arg_parser.add_argument("--file", required=True, type=Path, help="alias file")
46 arg_parser.add_argument(
47 "--dry-run", action="store_true", help="don't modify files, only print results"
48 )
49
50 parsed = arg_parser.parse_args()
51
52 if parsed.only_throws:
53 parsed.operate_on.append("throws")
54
55 if parsed.operate_on is None:
56 parsed.operate_on = ["aliases", "warnings", "throws"]
57
58 return parsed
59
60
61def get_date_lists(
62 txt: list[str], cutoffdate: datetimedate
63) -> tuple[list[str], list[str], list[str], list[str], list[str]]:
64 """get a list of lines in which the date is older than $cutoffdate"""
65 date_older_list: list[str] = []
66 date_older_throw_list: list[str] = []
67 date_older_warning_list: list[str] = []
68 date_sep_line_list: list[str] = []
69 date_too_complex_list: list[str] = []
70
71 for lineno, line in enumerate(txt, start=1):
72 line = line.rstrip()
73 my_date = None
74 for string in line.split():
75 string = string.strip(":")
76 try:
77 # strip ':' incase there is a string like 2019-11-01:
78 my_date = datetime.strptime(string, "%Y-%m-%d").date()
79 except ValueError:
80 try:
81 my_date = datetime.strptime(string, "%Y-%m").date()
82 except ValueError:
83 continue
84
85 if (
86 my_date is None
87 or my_date > cutoffdate
88 or "preserve, reason:" in line.lower()
89 ):
90 continue
91
92 if line.lstrip().startswith("inherit (") and ";" in line:
93 date_older_list.append(line)
94 elif "=" not in line:
95 date_sep_line_list.append(f"{lineno:>5} {line}")
96 # 'if' lines could be complicated
97 elif "if " in line and "if =" not in line:
98 date_too_complex_list.append(f"{lineno:>5} {line}")
99 elif "= with " in line:
100 date_too_complex_list.append(f"{lineno:>5} {line}")
101 elif "warnAlias" in line or "warning" in line:
102 if 'warnAlias "' in line:
103 date_older_warning_list.append(line)
104 else:
105 date_too_complex_list.append(f"{lineno:>5} {line}")
106 elif " = throw" in line:
107 date_older_throw_list.append(line)
108 else:
109 date_older_list.append(line)
110
111 return (
112 date_older_list,
113 date_sep_line_list,
114 date_too_complex_list,
115 date_older_throw_list,
116 date_older_warning_list,
117 )
118
119
120def convert(lines: list[str], convert_to: str) -> list[tuple[str, str]]:
121 """convert a list of lines to either "throws" or "warnings"."""
122 converted_lines = {}
123 for line in lines.copy():
124 indent: str = " " * (len(line) - len(line.lstrip()))
125
126 if "=" not in line:
127 assert "inherit (" in line
128 before, sep, after = line.partition("inherit (")
129 inside, sep, after = after.partition(")")
130 if not sep:
131 print(f"FAILED ON {line}")
132 continue
133 alias, *_ = after.strip().split(";")[0].split()
134 replacement = f"{inside.strip()}.{alias}"
135
136 else:
137 before_equal = ""
138 after_equal = ""
139 try:
140 before_equal, after_equal = (
141 x.strip() for x in line.split("=", maxsplit=2)
142 )
143 if after_equal.startswith("warnAlias"):
144 after_equal = after_equal.split("\"", maxsplit=3)[2].strip()
145 except ValueError as err:
146 print(err, line, "\n")
147 lines.remove(line)
148 continue
149
150 alias = before_equal
151 replacement = next(x.strip(";:") for x in after_equal.split())
152
153 alias_unquoted = alias.strip('"')
154 replacement = replacement.removeprefix("pkgs.")
155
156 if convert_to == "throws":
157 converted = (
158 f"{indent}{alias} = throw \"'{alias_unquoted}' has been"
159 f" renamed to/replaced by '{replacement}'\";"
160 f" # Converted to throw {datetime.today().strftime('%Y-%m-%d')}"
161 )
162 converted_lines[line] = converted
163 elif convert_to == "warnings":
164 converted = (
165 f"{indent}{alias} = warnAlias \"'{alias_unquoted}' has been"
166 f" renamed to/replaced by '{replacement}'\" {replacement};"
167 f" # Converted to warning {datetime.today().strftime('%Y-%m-%d')}"
168 )
169 converted_lines[line] = converted
170 else:
171 raise ValueError("'convert_to' must be either 'throws' or 'warnings'")
172
173 return converted_lines
174
175
176def generate_text_to_write(
177 txt: list[str],
178 converted_lines: dict[str, str],
179) -> list[str]:
180 """generate a list of text to be written to the aliasfile"""
181 text_to_write: list[str] = []
182 for line in txt:
183 text_to_append: str = ""
184 try:
185 new_line = converted_lines[line]
186 if new_line is not None:
187 text_to_write.append(f"{new_line}\n")
188 except KeyError:
189 text_to_write.append(f"{line}\n")
190 if text_to_append:
191 text_to_write.append(text_to_append)
192
193 return text_to_write
194
195
196def write_file(
197 aliasfile: Path,
198 text_to_write: list[str],
199) -> None:
200 """write file"""
201 temp_aliasfile = Path(f"{aliasfile}.raliases")
202 with open(temp_aliasfile, "w", encoding="utf-8") as far:
203 for line in text_to_write:
204 far.write(line)
205 print("\nChecking the syntax of the new aliasfile")
206 try:
207 subprocess.run(
208 ["nix-instantiate", "--eval", temp_aliasfile],
209 check=True,
210 stdout=subprocess.DEVNULL,
211 )
212 except subprocess.CalledProcessError:
213 print(
214 "\nSyntax check failed,",
215 "there may have been a line which only has\n"
216 'aliasname = "reason why";\n'
217 "when it should have been\n"
218 'aliasname = throw "reason why";',
219 )
220 temp_aliasfile.unlink()
221 return
222 shutil.move(f"{aliasfile}.raliases", aliasfile)
223 print(f"{aliasfile} modified! please verify with 'git diff'.")
224
225
226def main() -> None:
227 """main"""
228 args = process_args()
229
230 aliasfile = Path(args.file).absolute()
231 cutoffdate = (datetime.strptime(f"{args.year}-{args.month}-01", "%Y-%m-%d")).date()
232
233 txt: list[str] = (aliasfile.read_text(encoding="utf-8")).splitlines()
234
235 date_older_list: list[str] = []
236 date_older_warning_list: list[str] = []
237 date_older_throw_list: list[str] = []
238 date_sep_line_list: list[str] = []
239 date_too_complex_list: list[str] = []
240
241 (
242 date_older_list,
243 date_sep_line_list,
244 date_too_complex_list,
245 date_older_throw_list,
246 date_older_warning_list,
247 ) = get_date_lists(txt, cutoffdate)
248
249 converted_lines: dict[str, str] = {}
250
251 if date_older_list and "aliases" in args.operate_on:
252 converted_lines.update(convert(date_older_list, "warnings"))
253 print(" Will be converted to warnings. ".center(100, "-"))
254 for l_n in date_older_list:
255 print(l_n)
256
257 if date_older_warning_list and "warnings" in args.operate_on:
258 converted_lines.update(convert(date_older_warning_list, "throws"))
259 print(" Will be converted to throws. ".center(100, "-"))
260 for l_n in date_older_warning_list:
261 print(l_n)
262
263 if date_older_throw_list and "throws" in args.operate_on:
264 print(" Will be removed. ".center(100, "-"))
265 for l_n in date_older_throw_list:
266 converted_lines[l_n] = None
267 print(l_n)
268
269 if date_too_complex_list:
270 print(" Too complex, resolve manually. ".center(100, "-"))
271 for l_n in date_too_complex_list:
272 print(l_n)
273
274 if date_sep_line_list:
275 print(" On separate line, resolve manually. ".center(100, "-"))
276 for l_n in date_sep_line_list:
277 print(l_n)
278
279 if not args.dry_run:
280 text_to_write = generate_text_to_write(txt, converted_lines)
281 write_file(aliasfile, text_to_write)
282
283
284if __name__ == "__main__":
285 main()