def path_matches_single_pattern(path, pattern): """Return True if and only if the given path matches the given pattern. Both arguments (path and pattern) must be in our chroot-like format, i.e., start with a '/' and be normalized (no '..', etc.). A path matches a pattern if the path is either equal to the pattern or the pattern is a directory and path is contained in the directory. In particular, this means that the path '/foobar' does not match the pattern '/foo'. """ # check input convention if not (path.startswith(os.sep) and pattern.startswith(os.sep)): raise ValueError('Both file name ("{}") and pattern ("{}") must start with "{}".'.format(path, pattern, os.sep)) utils.ensure_normalized(path) utils.ensure_normalized(pattern) # perform actual pattern matching check if path == pattern: # if pattern is a path, then only the path itself can match return True elif path.startswith(pattern + os.sep): # if pattern is a subdirectory of root then path must # start with "pattern/" return True elif pattern == os.sep: # if a pattern is root itself then any path matches return True else: # nothing else matches return False
def parse_pattern_file(f): """Return a list of patterns parsed from the file object f The patterns are returned as a list of pairs (modifier, pattern), where modifier is either the constant INCLUDE or the constant EXCLUDE. pattern is the pathname affected by the inclusion / exclusion rule. The list is in the same order as the pattern file f, i.e., in order of priority (later rules override earlier rules). In case of an invalid pattern, the function raises a ValueError exception. """ res = [] # Note that in the regex, {} is replaced by os.sep. regex = re.compile('^([+-])\W+({}.*)$'.format(re.escape(os.sep))) for line in f: line = line.rstrip() if line.startswith('#'): continue else: r = regex.match(line.rstrip()) if not r: raise ValueError('Line "{}" is not a valid pattern.'.format( line)) modifier = r.groups()[0] pattern = r.groups()[1] utils.ensure_normalized(pattern) if modifier == '+': res.append((INCLUDE, pattern)) elif modifier == '-': res.append((EXCLUDE, pattern)) else: raise ValueError('Unknown pattern modifier "{}".'.format( modifier)) return res
def pattern_decision(path, patterns): """Compute the include / exclude decision for a given path and patterns. The parameter patterns is a list of patterns where each pattern is a pair of (decision, pattern_string) as returned by parse_pattern_file. The last matching pattern in the list determines the final decision. """ utils.ensure_normalized(path) res = INCLUDE for p in patterns: utils.ensure_normalized(p[1]) if p[0] != INCLUDE and p[0] != EXCLUDE: raise ValueError('Invalid pattern: unknown decision') if path_matches_single_pattern(path, p[1]): res = p[0] return res