From 42371181fcd296dad404c6b6e92e3108fb1793c7 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 11 Mar 2017 04:13:40 +0100 Subject: [PATCH] support switching the pattern style default in patterns file --- src/borg/archiver.py | 4 +++- src/borg/helpers.py | 37 ++++++++++++++++++++++++----------- src/borg/testsuite/helpers.py | 27 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 931933bc2..ce6c2bf21 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -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 diff --git a/src/borg/helpers.py b/src/borg/helpers.py index 9c112d116..db826d9fb 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -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) diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py index 57bf01759..4210cddda 100644 --- a/src/borg/testsuite/helpers.py +++ b/src/borg/testsuite/helpers.py @@ -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