comparison cutils/treesum.py @ 330:61cbae10103c

treesum: first minimal version of writing the treesum output in tabular format. BUGS: Reading tabular output is not yet supported.
author Franz Glasner <fzglas.hg@dom66.de>
date Fri, 28 Mar 2025 14:20:45 +0100
parents e2ae72792f56
children 9ee84624587f
comparison
equal deleted inserted replaced
329:e2ae72792f56 330:61cbae10103c
165 "--output", "-o", action="store", metavar="OUTPUT", 165 "--output", "-o", action="store", metavar="OUTPUT",
166 help="Put the checksum into given file. " 166 help="Put the checksum into given file. "
167 "If not given or if it is given as `-' then stdout is used.") 167 "If not given or if it is given as `-' then stdout is used.")
168 gp.add_argument( 168 gp.add_argument(
169 "--output-style", dest="output_style", default="tagged", 169 "--output-style", dest="output_style", default="tagged",
170 choices=("tagged", "tag"), 170 choices=("tagged", "tag", "tabular", "tab"),
171 help=""" 171 help="""
172 Select the output style: "tagged" or "tag" selects BSD style tagged format. 172 Select the output style: "tagged" or "tag" selects a more BSD style tagged
173 format. "tabular" or "tab" select a more GNU style tabular format.
173 Default is "tagged". 174 Default is "tagged".
174 """) 175 """)
175 gp.add_argument( 176 gp.add_argument(
176 "--physical", "-P", action=SymlinkAction, dest="follow_symlinks", 177 "--physical", "-P", action=SymlinkAction, dest="follow_symlinks",
177 const=FollowSymlinkConfig(False, False, False), 178 const=FollowSymlinkConfig(False, False, False),
454 "every kind of every item in `fnmatch_filters' must be" 455 "every kind of every item in `fnmatch_filters' must be"
455 " \"include\", \"exclude\" or \"accept-treesum\"" 456 " \"include\", \"exclude\" or \"accept-treesum\""
456 ) 457 )
457 if generator not in ("normal", "full", "none"): 458 if generator not in ("normal", "full", "none"):
458 raise ValueError("given generator `%s' not allowed" % (generator, )) 459 raise ValueError("given generator `%s' not allowed" % (generator, ))
459 if output_style not in ("tagged", "tag"): 460 if output_style not in ("tagged", "tag", "tabular", "tab"):
460 raise ValueError( 461 raise ValueError(
461 "given output_style `%s' not allowed" % (output_style,)) 462 "given output_style `%s' not allowed" % (output_style,))
462 463
463 # Not following symlinks to files is not yet supported: reset to True 464 # Not following symlinks to files is not yet supported: reset to True
464 # if not follow_symlinks.file: 465 # if not follow_symlinks.file:
528 out_cm = open(opts.output, "wb") 529 out_cm = open(opts.output, "wb")
529 530
530 fnmatcher = fnmatch.FnMatcher.build_from_commandline_patterns( 531 fnmatcher = fnmatch.FnMatcher.build_from_commandline_patterns(
531 opts.fnmatch_filters) 532 opts.fnmatch_filters)
532 533
533 assert opts.output_style in ("tagged", "tag") 534 if opts.output_style in ("tagged", "tag"):
535 writerstyle = TaggedTreesumWriter
536 elif opts.output_style in ("tabular", "tab"):
537 writerstyle = TabularTreesumWriter
538 else:
539 raise NotImplementedError("`output_style'")
534 540
535 with out_cm as outfp: 541 with out_cm as outfp:
536 writer = TaggedTreesumWriter(outfp) 542 writer = writerstyle(outfp)
537 for d in opts.directories: 543 for d in opts.directories:
538 V1DirectoryTreesumGenerator( 544 V1DirectoryTreesumGenerator(
539 opts.algorithm, opts.mmap, opts.base64, 545 opts.algorithm, opts.mmap, opts.base64,
540 opts.follow_symlinks, 546 opts.follow_symlinks,
541 opts.generator, 547 opts.generator,
1439 self.write(b"CRC32 = ") 1445 self.write(b"CRC32 = ")
1440 self.writeln(util.b(crc)) 1446 self.writeln(util.b(crc))
1441 self.flush() 1447 self.flush()
1442 1448
1443 1449
1450 class TabularTreesumWriter(WriterBase):
1451
1452 """Writer to write treesum digest files in a format similar to tabular
1453 GNU digest files.
1454
1455 Provides high-level methods to write data lines.
1456
1457 """
1458
1459 def __init__(self, outfp, **kwds):
1460 super(TabularTreesumWriter, self).__init__(outfp)
1461
1462 def start(self, version):
1463 """Begin a new block, reset the current CRC and write the VERSION
1464 tag.
1465
1466 """
1467 self.reset_crc()
1468 self.write(b"VERSION\t")
1469 self.writeln(util.b(version))
1470
1471 def write_comment(self, comment):
1472 self.write(b"COMMENT\t")
1473 self.writeln(util.b(comment, "utf-8"))
1474
1475 def write_generator(self, generator):
1476 self.write(b"GENERATOR\t")
1477 self.writeln(util.b(generator, "utf-8"))
1478
1479 def write_error(self, error):
1480 self.write(b"ERROR\t")
1481 self.writeln(util.b(error, "utf-8"))
1482
1483 def write_fsencoding(self, encoding):
1484 self.write(b"FSENCODING\t")
1485 self.writeln(util.b(encoding))
1486
1487 def write_fnmatch_pattern(self, action, kind, pattern):
1488 self.write(b"FNMATCH\t")
1489 self.write(util.b(action))
1490 self.write(b": ")
1491 self.write(util.b(kind))
1492 self.write(b":")
1493 self.writeln(util.b(pattern, "utf-8"))
1494
1495 def write_flags(self, flags):
1496 self.write(b"FLAGS\t")
1497 if isinstance(flags, (str, bytes)):
1498 self.writeln(util.b(flags))
1499 else:
1500 flags.sort()
1501 self.writeln(util.b(",".join(flags)))
1502
1503 def write_timestamp(self, ts):
1504 self.write(b"TIMESTAMP\t")
1505 self.writeln(util.b(str(ts)))
1506
1507 def write_isotimestamp(self, ts):
1508 self.write(b"ISOTIMESTAMP\t")
1509 self.writeln(util.b(ts))
1510
1511 def write_root(self, root):
1512 assert isinstance(root, bytes)
1513 self.write(b"ROOT\t")
1514 self.writeln(root)
1515
1516 def write_size(self, filename, sz):
1517 assert isinstance(filename, bytes)
1518 if sz is not None:
1519 self.write(util.b(str(sz)))
1520 self.write(b"\t")
1521 self.writeln(filename)
1522
1523 def write_accept_treesum_file(self, filename):
1524 assert isinstance(filename, bytes)
1525 self.write(b"ACCEPT-TREESUM\t")
1526 self.writeln(filename)
1527
1528 def write_file_digest(self, algorithm, filename, digest,
1529 use_base64=False, size=None):
1530 assert isinstance(filename, bytes)
1531 if digest is not None:
1532 digest = (base64.b64encode(digest)
1533 if use_base64
1534 else binascii.hexlify(digest))
1535 self.write(util.b(algorithm))
1536 self.write(b":")
1537 if digest is not None:
1538 self.write(digest)
1539 self.write(b"\t")
1540 if size is not None:
1541 self.write(util.b(str(size)))
1542 self.write(b"\t")
1543 self.writeln(filename)
1544
1545 def finish(self):
1546 """Finish a block and write the current CRC"""
1547 crc = self.crc.hexdigest()
1548 self.write(b"CRC32\t")
1549 self.writeln(util.b(crc))
1550 self.flush()
1551
1552
1444 class TreesumReader(object): 1553 class TreesumReader(object):
1445 1554
1446 """Reader to read and/or verify treesum digest files. 1555 """Reader to read and/or verify treesum digest files.
1447 1556
1448 Supports the iterator and context manager protocol. 1557 Supports the iterator and context manager protocol.