Mercurial > hgrepos > Python > apps > py-cutils
comparison cutils/shasum.py @ 83:05e2bf4796fd
Add an option "--checklist-allow-distinfo" to allow FreeBSD "distinfo" formatted files as checkfiles.
This effectively ignores SIZE() and TIMESTAMP = like lines instead of
throwing errors.
This allows to use a package's distinfo file directoy as checkfile.
| author | Franz Glasner <f.glasner@feldmann-mg.com> |
|---|---|
| date | Thu, 10 Mar 2022 16:17:53 +0100 |
| parents | 8708c34e2723 |
| children | 163de6dd6e05 |
comparison
equal
deleted
inserted
replaced
| 82:7ddc249404d5 | 83:05e2bf4796fd |
|---|---|
| 77 aparser.add_argument( | 77 aparser.add_argument( |
| 78 "--checklist", "-C", metavar="CHECKLIST", | 78 "--checklist", "-C", metavar="CHECKLIST", |
| 79 help="""Compare the checksum of each FILE against the checksums in | 79 help="""Compare the checksum of each FILE against the checksums in |
| 80 the CHECKLIST. Any specified FILE that is not listed in the CHECKLIST will | 80 the CHECKLIST. Any specified FILE that is not listed in the CHECKLIST will |
| 81 generate an error.""") | 81 generate an error.""") |
| 82 aparser.add_argument( | |
| 83 "--checklist-allow-distinfo", action="store_true", | |
| 84 dest="allow_distinfo", | |
| 85 help='Allow FreeBSD "distinfo" formatted checklists: ignore SIZE and TIMESTAMP lines.') | |
| 82 | 86 |
| 83 aparser.add_argument( | 87 aparser.add_argument( |
| 84 "--reverse", "-r", action="store_false", dest="bsd", default=False, | 88 "--reverse", "-r", action="store_false", dest="bsd", default=False, |
| 85 help="Explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") | 89 help="Explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") |
| 86 aparser.add_argument( | 90 aparser.add_argument( |
| 258 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): | 262 if not opts.files or (len(opts.files) == 1 and opts.files[0] == '-'): |
| 259 for checkline in sys.stdin: | 263 for checkline in sys.stdin: |
| 260 if not checkline: | 264 if not checkline: |
| 261 continue | 265 continue |
| 262 r, fn, tag = handle_checkline(opts, checkline) | 266 r, fn, tag = handle_checkline(opts, checkline) |
| 267 if tag in ("SIZE", "TIMESTAMP"): | |
| 268 assert opts.allow_distinfo | |
| 269 continue | |
| 263 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) | 270 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) |
| 264 if r != "ok" and exit_code == 0: | 271 if r != "ok" and exit_code == 0: |
| 265 exit_code = 1 | 272 exit_code = 1 |
| 266 else: | 273 else: |
| 267 for fn in opts.files: | 274 for fn in opts.files: |
| 268 with io.open(fn, "rt", encoding="utf-8") as checkfile: | 275 with io.open(fn, "rt", encoding="utf-8") as checkfile: |
| 269 for checkline in checkfile: | 276 for checkline in checkfile: |
| 270 if not checkline: | 277 if not checkline: |
| 271 continue | 278 continue |
| 272 r, fn, tag = handle_checkline(opts, checkline) | 279 r, fn, tag = handle_checkline(opts, checkline) |
| 280 if tag in ("SIZE", "TIMESTAMP"): | |
| 281 assert opts.allow_distinfo | |
| 282 continue | |
| 273 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) | 283 print("{}: {}: {}".format(tag, fn, r.upper()), file=dest) |
| 274 if r != "ok" and exit_code == 0: | 284 if r != "ok" and exit_code == 0: |
| 275 exit_code = 1 | 285 exit_code = 1 |
| 276 return exit_code | 286 return exit_code |
| 277 | 287 |
| 286 parts = parse_digest_line(opts, line) | 296 parts = parse_digest_line(opts, line) |
| 287 if not parts: | 297 if not parts: |
| 288 raise ValueError( | 298 raise ValueError( |
| 289 "improperly formatted digest line: {}".format(line)) | 299 "improperly formatted digest line: {}".format(line)) |
| 290 tag, algo, fn, digest = parts | 300 tag, algo, fn, digest = parts |
| 301 if tag in ("SIZE", "TIMESTAMP"): | |
| 302 assert opts.allow_distinfo | |
| 303 return (None, None, tag) | |
| 291 try: | 304 try: |
| 292 d = compute_digest_file(algo, fn) | 305 d = compute_digest_file(algo, fn) |
| 293 if compare_digests_equal(d, digest, algo): | 306 if compare_digests_equal(d, digest, algo): |
| 294 return ("ok", fn, tag) | 307 return ("ok", fn, tag) |
| 295 else: | 308 else: |
| 310 continue | 323 continue |
| 311 parts = parse_digest_line(opts, checkline) | 324 parts = parse_digest_line(opts, checkline) |
| 312 if not parts: | 325 if not parts: |
| 313 raise ValueError( | 326 raise ValueError( |
| 314 "improperly formatted digest line: {}".format(checkline)) | 327 "improperly formatted digest line: {}".format(checkline)) |
| 328 if parts[0] in ("SIZE", "TIMESTAMP"): | |
| 329 assert opts.allow_distinfo | |
| 330 continue | |
| 315 fn = normalize_filename(parts[2], strip_leading_dot_slash=True) | 331 fn = normalize_filename(parts[2], strip_leading_dot_slash=True) |
| 316 if fn in filenames: | 332 if fn in filenames: |
| 317 return parts | 333 return parts |
| 318 else: | 334 else: |
| 319 return None | 335 return None |
| 320 | 336 |
| 321 | 337 |
| 322 def parse_digest_line(opts, line): | 338 def parse_digest_line(opts, line): |
| 323 """Parse a `line` of a digest file and return its parts. | 339 """Parse a `line` of a digest file and return its parts. |
| 340 | |
| 341 This is rather strict. But if `opts.allow_distinfo` is `True` then | |
| 342 some additional keywords ``SIZE`` and ``TIMESTAMP``are recignized | |
| 343 and returned. The caller is responsible to handle them. | |
| 324 | 344 |
| 325 :return: a tuple of the normalized algorithm tag, the algorithm | 345 :return: a tuple of the normalized algorithm tag, the algorithm |
| 326 constructor, the filename and the hex digest; | 346 constructor, the filename and the hex digest; |
| 327 if `line` cannot be parsed successfully `None` is returned | 347 if `line` cannot be parsed successfully `None` is returned |
| 328 :rtype: tuple(str, obj, str, str) or None | 348 :rtype: tuple(str, obj, str, str) or None |
| 333 # determine checkfile format (BSD or coreutils) | 353 # determine checkfile format (BSD or coreutils) |
| 334 # BSD? | 354 # BSD? |
| 335 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line) | 355 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line) |
| 336 if mo: | 356 if mo: |
| 337 # (tag, algorithm, filename, digest) | 357 # (tag, algorithm, filename, digest) |
| 358 if opts.allow_distinfo: | |
| 359 if mo.group(1) == "SIZE": | |
| 360 return ("SIZE", None, None, mo.group(3)) | |
| 338 return (mo.group(1), | 361 return (mo.group(1), |
| 339 algotag2algotype(mo.group(1)), | 362 algotag2algotype(mo.group(1)), |
| 340 mo.group(2), | 363 mo.group(2), |
| 341 mo.group(3)) | 364 mo.group(3)) |
| 342 else: | 365 else: |
| 366 if opts.allow_distinfo: | |
| 367 mo = re.search(r"\ATIMESTAMP\s*=\s*([0-9]+)\s*\n\Z", line) | |
| 368 if mo: | |
| 369 return ("TIMESTAMP", None, None, mo.group(1)) | |
| 370 | |
| 343 # coreutils? | 371 # coreutils? |
| 344 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line) | 372 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line) |
| 345 if mo: | 373 if mo: |
| 346 # (tag, algorithm, filename, digest) | 374 # (tag, algorithm, filename, digest) |
| 347 return (opts.algorithm[1], | 375 return (opts.algorithm[1], |
