blob: 60fa330c25a2bf6a8fff65a0487bfef3a7d2ed81 [file] [log] [blame]
 import itertools import re import os end_space = re.compile(r"([^\\]\s)*\$") def fnmatch_translate(pat, path_name=False): parts = [] seq = False i = 0 if pat[0] == "/" or path_name: parts.append("^") any_char = "[^/]" if pat[0] == "/": pat = pat[1:] else: any_char = "." parts.append("^(?:.*/)?") while i < len(pat): c = pat[i] if c == "\\": if i < len(pat) - 1: i += 1 c = pat[i] parts.append(re.escape(c)) else: raise ValueError elif seq: if c == "]": seq = False # First two cases are to deal with the case where / is the only character # in the sequence but path_name is True so it shouldn't match anything if parts[-1] == "[": parts = parts[:-1] elif parts[-1] == "^" and parts[-2] == "[": parts = parts[:-2] else: parts.append(c) elif c == "-": parts.append(c) elif not (path_name and c == "/"): parts += re.escape(c) elif c == "[": parts.append("[") if i < len(pat) - 1 and pat[i+1] in ("!", "^"): parts.append("^") i += 1 seq = True elif c == "*": if i < len(pat) - 1 and pat[i+1] == "*": parts.append(any_char + "*") i += 1 if i < len(pat) - 1 and pat[i+1] == "*": raise ValueError else: parts.append(any_char + "*") elif c == "?": parts.append(any_char) else: parts.append(re.escape(c)) i += 1 if seq: raise ValueError parts.append("\$") try: return re.compile("".join(parts)) except: raise def parse_line(line): line = line.rstrip() if not line or line[0] == "#": return invert = line[0] == "!" if invert: line = line[1:] dir_only = line[-1] == "/" if dir_only: line = line[:-1] return invert, dir_only, fnmatch_translate(line, "/" in line) class PathFilter(object): def __init__(self, root, extras=None): if root: ignore_path = os.path.join(root, ".gitignore") else: ignore_path = None if not ignore_path and not extras: self.trivial = True return self.trivial = False self.rules_file = [] self.rules_dir = [] if extras is None: extras = [] if ignore_path and os.path.exists(ignore_path): self._read_ignore(ignore_path) for item in extras: self._read_line(item) def _read_ignore(self, ignore_path): with open(ignore_path) as f: for line in f: self._read_line(line) def _read_line(self, line): parsed = parse_line(line) if not parsed: return invert, dir_only, regexp = parsed if dir_only: self.rules_dir.append((regexp, invert)) else: self.rules_file.append((regexp, invert)) def __call__(self, path): if os.path.sep != "/": path = path.replace(os.path.sep, "/") if self.trivial: return True path_is_dir = path[-1] == "/" if path_is_dir: path = path[:-1] rules = self.rules_dir else: rules = self.rules_file include = True for regexp, invert in rules: if not include and invert and regexp.match(path): include = True elif include and not invert and regexp.match(path): include = False return include