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