nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at python-updates 285 lines 9.6 kB view raw
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()