Mercurial > hgrepos > Python > apps > py-cutils
changeset 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 |
| files | cutils/treesum.py cutils/util/fnmatch.py |
| diffstat | 2 files changed, 92 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/cutils/treesum.py Tue Mar 04 11:26:22 2025 +0100 +++ b/cutils/treesum.py Tue Mar 04 16:30:10 2025 +0100 @@ -30,6 +30,7 @@ from . import util from .util import cm from .util import digest +from .util import fnmatch from .util import walk from .util.crc32 import crc32 @@ -65,6 +66,11 @@ help="Put given comment COMMENT into the output as \"COMMENT\". " "Can be given more than once.") gp.add_argument( + "--exclude", "-X", action=PatternMatchAction, kind="exclude", + dest="fnmatch_filters", metavar="PATTERN", + help="""Exclude names matching the given PATTERN. +For help on PATTERN use \"help patterns\".""") + gp.add_argument( "--follow-directory-symlinks", "-l", action=SymlinkAction, const="follow-directory-symlinks", default=FollowSymlinkConfig(False, False, True), @@ -99,6 +105,11 @@ (--physical, --logical, -p, --no-follow-directory-symlinks, --no-follow-file-symlinks, et al.).""") gp.add_argument( + "--include", "-I", action=PatternMatchAction, kind="include", + dest="fnmatch_filters", metavar="PATTERN", + help="""Include names matching the given PATTERN. +For help on PATTERN use \"help patterns\".""") + gp.add_argument( "--logical", "-L", action=SymlinkAction, dest="follow_symlinks", const=FollowSymlinkConfig(True, True, True), help="""Follow symbolic links everywhere: on command line @@ -248,6 +259,12 @@ description="Show this help message or a subcommand's help and exit.") hparser.add_argument("help_command", nargs='?', metavar="COMMAND") + patparser = subparsers.add_parser( + "patterns", + help="Show the help for PATTERNs and exit", + description=fnmatch.HELP_DESCRIPTION, + formatter_class=argparse.RawDescriptionHelpFormatter) + vparser = subparsers.add_parser( "version", help="Show the program's version number and exit", @@ -274,6 +291,8 @@ vparser.print_help() elif opts.help_command == "help": hparser.print_help() + elif opts.help_command == "patterns": + patparser.print_help() else: parser.print_help() return 0 @@ -289,7 +308,7 @@ ) logging.captureWarnings(True) - return treesum(opts) + return treesum(opts, patparser=patparser) FollowSymlinkConfig = collections.namedtuple("FollowSymlinkConfig", @@ -356,11 +375,39 @@ setattr(namespace, self.dest, curval) +class PatternMatchAction(argparse.Action): + + def __init__(self, *args, **kwargs): + if "nargs" in kwargs: + raise argparse.ArgumentError(None, "`nargs' not allowed") + if "type" in kwargs: + raise argparse.ArgumentError(None, "`type' not allowed") + kwargs["nargs"] = 1 + + self.__kind = kwargs.pop("kind", None) + if self.__kind is None: + raise argparse.ArgumentError(None, "`kind' is required") + if self.__kind not in ("exclude", "include"): + raise argparse.ArgumentError( + None, "`kind' must be one of `include' or `exclude'") + + super(PatternMatchAction, self).__init__(*args, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + items = getattr(namespace, self.dest, None) + if items is None: + items = [] + setattr(namespace, self.dest, items) + for v in values: + items.append((self.__kind, v)) + + def gen_generate_opts(directories=[], algorithm=util.default_algotag(), append_output=False, base64=False, comment=[], + fnmatch_filters=[], follow_symlinks=FollowSymlinkConfig(False, False, False), full_mode=False, generator="normal", @@ -375,6 +422,19 @@ utf8=False): if not isinstance(follow_symlinks, FollowSymlinkConfig): raise TypeError("`follow_symlinks' must be a FollowSymlinkConfig") + if not isinstance(fnmatch_filters, (list, tuple, type(None))): + raise TypeError("`fnmatch_filters' must be a sequence (list, tuple)") + if fnmatch_filters: + for f in fnmatch_filters: + if not isinstance(f, (tuple, list)): + raise TypeError( + "items in `fnmatch_filters' must be tuples or lists") + if f[0] not in ("exclude", "include"): + raise ValueError( + "every kind of every item in `fnmatch_filters' must be" + " \"include\" or \"exclude\"" + ) + # Not following symlinks to files is not yet supported: reset to True # if not follow_symlinks.file: # follow_symlinks = follow_symlinks._make([follow_symlinks.command_line, @@ -387,6 +447,7 @@ append_output=append_output, base64=base64, comment=comment, + fnmatch_filters=fnmatch_filters, follow_symlinks=follow_symlinks, generator=generator, logical=logical, @@ -409,12 +470,14 @@ return opts -def treesum(opts): +def treesum(opts, patparser=None): # XXX TBD: opts.check and opts.checklist (as in shasum.py) if opts.subcommand in ("generate", "gen"): return generate_treesum(opts) elif opts.subcommand == "info": return print_treesum_digestfile_infos(opts) + elif opts.subcommand == "patterns": + patparser.print_help() else: raise RuntimeError( "command `{}' not yet handled".format(opts.subcommand))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cutils/util/fnmatch.py Tue Mar 04 16:30:10 2025 +0100 @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# :- +# :Copyright: (c) 2020-2025 Franz Glasner +# :License: BSD-3-Clause +# :- +r"""File name matching. + +""" + +from __future__ import print_function, absolute_import + + +__all__ = [] + + +HELP_DESCRIPTION = """\ +PATTERNs +======== + + glob: case-sensitive, anchored at the begin and end + iglob: case-insensitive variant of "glob" + re: regular expression + path: plain text example (rooted), can be a file or a directory or a prefix + thereof + filepath: exactly a single file, relative to the root of the tree + +"""
