support switching the pattern style default in patterns file

This commit is contained in:
Thomas Waldmann 2017-03-11 04:13:40 +01:00
parent d1738ec315
commit 42371181fc
3 changed files with 56 additions and 12 deletions

View File

@ -1663,7 +1663,7 @@ class Archiver:
Note that the default pattern style for `--pattern` and `--patterns-from` is
shell style (`sh:`), so those patterns behave similar to rsync include/exclude
patterns.
patterns. The pattern style can be set via the `P` prefix.
Patterns (`--pattern`) and excludes (`--exclude`) from the command line are
considered first (in the order of appearance). Then patterns from `--patterns-from`
@ -1671,6 +1671,8 @@ class Archiver:
An example `--patterns-from` file could look like that::
# "sh:" pattern style is the default, so the following line is not needed:
P sh
R /
# can be rebuild
- /home/*/.cache

View File

@ -391,18 +391,23 @@ def parse_timestamp(timestamp):
return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc)
def parse_add_pattern(patternstr, roots, patterns):
def parse_add_pattern(patternstr, roots, patterns, fallback):
"""Parse a pattern string and add it to roots or patterns depending on the pattern type."""
pattern = parse_inclexcl_pattern(patternstr)
pattern = parse_inclexcl_pattern(patternstr, fallback=fallback)
if pattern.ptype is RootPath:
roots.append(pattern.pattern)
elif pattern.ptype is PatternStyle:
fallback = pattern.pattern
else:
patterns.append(pattern)
return fallback
def load_pattern_file(fileobj, roots, patterns):
def load_pattern_file(fileobj, roots, patterns, fallback=None):
if fallback is None:
fallback = ShellPattern # ShellPattern is defined later in this module
for patternstr in clean_lines(fileobj):
parse_add_pattern(patternstr, roots, patterns)
fallback = parse_add_pattern(patternstr, roots, patterns, fallback)
def load_exclude_file(fileobj, patterns):
@ -415,7 +420,7 @@ class ArgparsePatternAction(argparse.Action):
super().__init__(nargs=nargs, **kw)
def __call__(self, parser, args, values, option_string=None):
parse_add_pattern(values[0], args.paths, args.patterns)
parse_add_pattern(values[0], args.paths, args.patterns, ShellPattern)
class ArgparsePatternFileAction(argparse.Action):
@ -614,6 +619,14 @@ _PATTERN_STYLE_BY_PREFIX = dict((i.PREFIX, i) for i in _PATTERN_STYLES)
InclExclPattern = namedtuple('InclExclPattern', 'pattern ptype')
RootPath = object()
PatternStyle = object()
def get_pattern_style(prefix):
try:
return _PATTERN_STYLE_BY_PREFIX[prefix]
except KeyError:
raise ValueError("Unknown pattern style: {}".format(prefix)) from None
def parse_pattern(pattern, fallback=FnmatchPattern):
@ -621,14 +634,9 @@ def parse_pattern(pattern, fallback=FnmatchPattern):
"""
if len(pattern) > 2 and pattern[2] == ":" and pattern[:2].isalnum():
(style, pattern) = (pattern[:2], pattern[3:])
cls = _PATTERN_STYLE_BY_PREFIX.get(style, None)
if cls is None:
raise ValueError("Unknown pattern style: {}".format(style))
cls = get_pattern_style(style)
else:
cls = fallback
return cls(pattern)
@ -646,6 +654,8 @@ def parse_inclexcl_pattern(pattern, fallback=ShellPattern):
'+': True,
'R': RootPath,
'r': RootPath,
'P': PatternStyle,
'p': PatternStyle,
}
try:
ptype = type_prefix_map[pattern[0]]
@ -656,6 +666,11 @@ def parse_inclexcl_pattern(pattern, fallback=ShellPattern):
raise argparse.ArgumentTypeError("Unable to parse pattern: {}".format(pattern))
if ptype is RootPath:
pobj = pattern
elif ptype is PatternStyle:
try:
pobj = get_pattern_style(pattern)
except ValueError:
raise argparse.ArgumentTypeError("Unable to parse pattern: {}".format(pattern))
else:
pobj = parse_pattern(pattern, fallback)
return InclExclPattern(pobj, ptype)

View File

@ -1,5 +1,6 @@
import argparse
import hashlib
import io
import os
import sys
from datetime import datetime, timezone, timedelta
@ -496,6 +497,32 @@ def test_load_patterns_from_file(tmpdir, lines, expected_roots, expected_numpatt
assert numpatterns == expected_numpatterns
def test_switch_patterns_style():
patterns = """\
+0_initial_default_is_shell
p fm
+1_fnmatch
P re
+2_regex
+3_more_regex
P pp
+4_pathprefix
p fm
p sh
+5_shell
"""
pattern_file = io.StringIO(patterns)
roots, patterns = [], []
load_pattern_file(pattern_file, roots, patterns)
assert len(patterns) == 6
assert isinstance(patterns[0].pattern, ShellPattern)
assert isinstance(patterns[1].pattern, FnmatchPattern)
assert isinstance(patterns[2].pattern, RegexPattern)
assert isinstance(patterns[3].pattern, RegexPattern)
assert isinstance(patterns[4].pattern, PathPrefixPattern)
assert isinstance(patterns[5].pattern, ShellPattern)
@pytest.mark.parametrize("lines", [
(["X /data"]), # illegal pattern type prefix
(["/data"]), # need a pattern type prefix