Mercurial > hgrepos > Python > apps > py-cutils
comparison cutils/treesum.py @ 300:1fc117f5f9a1
treesum: Implement --include/--exclude commandline parsing for file name inclusion and exclusion.
BUGS:
- Real filename matching not yet implemented
- Pattern description only very rudimentary
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Tue, 04 Mar 2025 16:30:10 +0100 |
| parents | bcbc68d8aa12 |
| children | d246b631b85a |
comparison
equal
deleted
inserted
replaced
| 299:bcbc68d8aa12 | 300:1fc117f5f9a1 |
|---|---|
| 28 | 28 |
| 29 from . import (__version__, __revision__) | 29 from . import (__version__, __revision__) |
| 30 from . import util | 30 from . import util |
| 31 from .util import cm | 31 from .util import cm |
| 32 from .util import digest | 32 from .util import digest |
| 33 from .util import fnmatch | |
| 33 from .util import walk | 34 from .util import walk |
| 34 from .util.crc32 import crc32 | 35 from .util.crc32 import crc32 |
| 35 | 36 |
| 36 | 37 |
| 37 def main(argv=None): | 38 def main(argv=None): |
| 63 gp.add_argument( | 64 gp.add_argument( |
| 64 "--comment", action="append", default=[], | 65 "--comment", action="append", default=[], |
| 65 help="Put given comment COMMENT into the output as \"COMMENT\". " | 66 help="Put given comment COMMENT into the output as \"COMMENT\". " |
| 66 "Can be given more than once.") | 67 "Can be given more than once.") |
| 67 gp.add_argument( | 68 gp.add_argument( |
| 69 "--exclude", "-X", action=PatternMatchAction, kind="exclude", | |
| 70 dest="fnmatch_filters", metavar="PATTERN", | |
| 71 help="""Exclude names matching the given PATTERN. | |
| 72 For help on PATTERN use \"help patterns\".""") | |
| 73 gp.add_argument( | |
| 68 "--follow-directory-symlinks", "-l", action=SymlinkAction, | 74 "--follow-directory-symlinks", "-l", action=SymlinkAction, |
| 69 const="follow-directory-symlinks", | 75 const="follow-directory-symlinks", |
| 70 default=FollowSymlinkConfig(False, False, True), | 76 default=FollowSymlinkConfig(False, False, True), |
| 71 dest="follow_symlinks", | 77 dest="follow_symlinks", |
| 72 help="""Follow symbolic links to directories when walking a | 78 help="""Follow symbolic links to directories when walking a |
| 96 help="""Follow symbolic links given on the command line but do | 102 help="""Follow symbolic links given on the command line but do |
| 97 not follow symlinks while traversing the directory tree. | 103 not follow symlinks while traversing the directory tree. |
| 98 Overwrites any other symlink related options | 104 Overwrites any other symlink related options |
| 99 (--physical, --logical, -p, --no-follow-directory-symlinks, | 105 (--physical, --logical, -p, --no-follow-directory-symlinks, |
| 100 --no-follow-file-symlinks, et al.).""") | 106 --no-follow-file-symlinks, et al.).""") |
| 107 gp.add_argument( | |
| 108 "--include", "-I", action=PatternMatchAction, kind="include", | |
| 109 dest="fnmatch_filters", metavar="PATTERN", | |
| 110 help="""Include names matching the given PATTERN. | |
| 111 For help on PATTERN use \"help patterns\".""") | |
| 101 gp.add_argument( | 112 gp.add_argument( |
| 102 "--logical", "-L", action=SymlinkAction, dest="follow_symlinks", | 113 "--logical", "-L", action=SymlinkAction, dest="follow_symlinks", |
| 103 const=FollowSymlinkConfig(True, True, True), | 114 const=FollowSymlinkConfig(True, True, True), |
| 104 help="""Follow symbolic links everywhere: on command line | 115 help="""Follow symbolic links everywhere: on command line |
| 105 arguments and -- while walking -- directory and file symbolic links. | 116 arguments and -- while walking -- directory and file symbolic links. |
| 245 hparser = subparsers.add_parser( | 256 hparser = subparsers.add_parser( |
| 246 "help", | 257 "help", |
| 247 help="Show this help message or a subcommand's help and exit", | 258 help="Show this help message or a subcommand's help and exit", |
| 248 description="Show this help message or a subcommand's help and exit.") | 259 description="Show this help message or a subcommand's help and exit.") |
| 249 hparser.add_argument("help_command", nargs='?', metavar="COMMAND") | 260 hparser.add_argument("help_command", nargs='?', metavar="COMMAND") |
| 261 | |
| 262 patparser = subparsers.add_parser( | |
| 263 "patterns", | |
| 264 help="Show the help for PATTERNs and exit", | |
| 265 description=fnmatch.HELP_DESCRIPTION, | |
| 266 formatter_class=argparse.RawDescriptionHelpFormatter) | |
| 250 | 267 |
| 251 vparser = subparsers.add_parser( | 268 vparser = subparsers.add_parser( |
| 252 "version", | 269 "version", |
| 253 help="Show the program's version number and exit", | 270 help="Show the program's version number and exit", |
| 254 description="Show the program's version number and exit.") | 271 description="Show the program's version number and exit.") |
| 272 infoparser.print_help() | 289 infoparser.print_help() |
| 273 elif opts.help_command == "version": | 290 elif opts.help_command == "version": |
| 274 vparser.print_help() | 291 vparser.print_help() |
| 275 elif opts.help_command == "help": | 292 elif opts.help_command == "help": |
| 276 hparser.print_help() | 293 hparser.print_help() |
| 294 elif opts.help_command == "patterns": | |
| 295 patparser.print_help() | |
| 277 else: | 296 else: |
| 278 parser.print_help() | 297 parser.print_help() |
| 279 return 0 | 298 return 0 |
| 280 | 299 |
| 281 # Reparse strictly | 300 # Reparse strictly |
| 287 stream=sys.stderr, | 306 stream=sys.stderr, |
| 288 format="[%(asctime)s][%(levelname)s][%(process)d:%(name)s] %(message)s" | 307 format="[%(asctime)s][%(levelname)s][%(process)d:%(name)s] %(message)s" |
| 289 ) | 308 ) |
| 290 logging.captureWarnings(True) | 309 logging.captureWarnings(True) |
| 291 | 310 |
| 292 return treesum(opts) | 311 return treesum(opts, patparser=patparser) |
| 293 | 312 |
| 294 | 313 |
| 295 FollowSymlinkConfig = collections.namedtuple("FollowSymlinkConfig", | 314 FollowSymlinkConfig = collections.namedtuple("FollowSymlinkConfig", |
| 296 ["command_line", | 315 ["command_line", |
| 297 "directory", | 316 "directory", |
| 354 assert False, "Implementation error: not yet implemented" | 373 assert False, "Implementation error: not yet implemented" |
| 355 | 374 |
| 356 setattr(namespace, self.dest, curval) | 375 setattr(namespace, self.dest, curval) |
| 357 | 376 |
| 358 | 377 |
| 378 class PatternMatchAction(argparse.Action): | |
| 379 | |
| 380 def __init__(self, *args, **kwargs): | |
| 381 if "nargs" in kwargs: | |
| 382 raise argparse.ArgumentError(None, "`nargs' not allowed") | |
| 383 if "type" in kwargs: | |
| 384 raise argparse.ArgumentError(None, "`type' not allowed") | |
| 385 kwargs["nargs"] = 1 | |
| 386 | |
| 387 self.__kind = kwargs.pop("kind", None) | |
| 388 if self.__kind is None: | |
| 389 raise argparse.ArgumentError(None, "`kind' is required") | |
| 390 if self.__kind not in ("exclude", "include"): | |
| 391 raise argparse.ArgumentError( | |
| 392 None, "`kind' must be one of `include' or `exclude'") | |
| 393 | |
| 394 super(PatternMatchAction, self).__init__(*args, **kwargs) | |
| 395 | |
| 396 def __call__(self, parser, namespace, values, option_string=None): | |
| 397 items = getattr(namespace, self.dest, None) | |
| 398 if items is None: | |
| 399 items = [] | |
| 400 setattr(namespace, self.dest, items) | |
| 401 for v in values: | |
| 402 items.append((self.__kind, v)) | |
| 403 | |
| 404 | |
| 359 def gen_generate_opts(directories=[], | 405 def gen_generate_opts(directories=[], |
| 360 algorithm=util.default_algotag(), | 406 algorithm=util.default_algotag(), |
| 361 append_output=False, | 407 append_output=False, |
| 362 base64=False, | 408 base64=False, |
| 363 comment=[], | 409 comment=[], |
| 410 fnmatch_filters=[], | |
| 364 follow_symlinks=FollowSymlinkConfig(False, False, False), | 411 follow_symlinks=FollowSymlinkConfig(False, False, False), |
| 365 full_mode=False, | 412 full_mode=False, |
| 366 generator="normal", | 413 generator="normal", |
| 367 logical=None, | 414 logical=None, |
| 368 minimal=None, | 415 minimal=None, |
| 373 print_size=False, | 420 print_size=False, |
| 374 size_only=False, | 421 size_only=False, |
| 375 utf8=False): | 422 utf8=False): |
| 376 if not isinstance(follow_symlinks, FollowSymlinkConfig): | 423 if not isinstance(follow_symlinks, FollowSymlinkConfig): |
| 377 raise TypeError("`follow_symlinks' must be a FollowSymlinkConfig") | 424 raise TypeError("`follow_symlinks' must be a FollowSymlinkConfig") |
| 425 if not isinstance(fnmatch_filters, (list, tuple, type(None))): | |
| 426 raise TypeError("`fnmatch_filters' must be a sequence (list, tuple)") | |
| 427 if fnmatch_filters: | |
| 428 for f in fnmatch_filters: | |
| 429 if not isinstance(f, (tuple, list)): | |
| 430 raise TypeError( | |
| 431 "items in `fnmatch_filters' must be tuples or lists") | |
| 432 if f[0] not in ("exclude", "include"): | |
| 433 raise ValueError( | |
| 434 "every kind of every item in `fnmatch_filters' must be" | |
| 435 " \"include\" or \"exclude\"" | |
| 436 ) | |
| 437 | |
| 378 # Not following symlinks to files is not yet supported: reset to True | 438 # Not following symlinks to files is not yet supported: reset to True |
| 379 # if not follow_symlinks.file: | 439 # if not follow_symlinks.file: |
| 380 # follow_symlinks = follow_symlinks._make([follow_symlinks.command_line, | 440 # follow_symlinks = follow_symlinks._make([follow_symlinks.command_line, |
| 381 # follow_symlinks.directory, | 441 # follow_symlinks.directory, |
| 382 # True]) | 442 # True]) |
| 385 directories=directories, | 445 directories=directories, |
| 386 algorithm=util.argv2algo(algorithm), | 446 algorithm=util.argv2algo(algorithm), |
| 387 append_output=append_output, | 447 append_output=append_output, |
| 388 base64=base64, | 448 base64=base64, |
| 389 comment=comment, | 449 comment=comment, |
| 450 fnmatch_filters=fnmatch_filters, | |
| 390 follow_symlinks=follow_symlinks, | 451 follow_symlinks=follow_symlinks, |
| 391 generator=generator, | 452 generator=generator, |
| 392 logical=logical, | 453 logical=logical, |
| 393 minimal=minimal, | 454 minimal=minimal, |
| 394 mmap=mmap, | 455 mmap=mmap, |
| 407 digest_files=digest_files, | 468 digest_files=digest_files, |
| 408 print_only_last_block=last) | 469 print_only_last_block=last) |
| 409 return opts | 470 return opts |
| 410 | 471 |
| 411 | 472 |
| 412 def treesum(opts): | 473 def treesum(opts, patparser=None): |
| 413 # XXX TBD: opts.check and opts.checklist (as in shasum.py) | 474 # XXX TBD: opts.check and opts.checklist (as in shasum.py) |
| 414 if opts.subcommand in ("generate", "gen"): | 475 if opts.subcommand in ("generate", "gen"): |
| 415 return generate_treesum(opts) | 476 return generate_treesum(opts) |
| 416 elif opts.subcommand == "info": | 477 elif opts.subcommand == "info": |
| 417 return print_treesum_digestfile_infos(opts) | 478 return print_treesum_digestfile_infos(opts) |
| 479 elif opts.subcommand == "patterns": | |
| 480 patparser.print_help() | |
| 418 else: | 481 else: |
| 419 raise RuntimeError( | 482 raise RuntimeError( |
| 420 "command `{}' not yet handled".format(opts.subcommand)) | 483 "command `{}' not yet handled".format(opts.subcommand)) |
| 421 | 484 |
| 422 | 485 |
