瀏覽代碼

Merge pull request #2288 from ThomasWaldmann/patterns-style-default

support setting the pattern style default in patterns file
TW 8 年之前
父節點
當前提交
16e6e3d989
共有 3 個文件被更改,包括 56 次插入12 次删除
  1. 3 1
      src/borg/archiver.py
  2. 26 11
      src/borg/helpers.py
  3. 27 0
      src/borg/testsuite/helpers.py

+ 3 - 1
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

+ 26 - 11
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)

+ 27 - 0
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
@@ -497,6 +498,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