import re import os import itertools from collections import defaultdict from typing import (Any, Dict, Iterable, List, MutableMapping, Optional, Pattern, Tuple, TypeVar,
Union, cast)
T = TypeVar('T')
end_space = re.compile(r"([^\\]\s)*$")
def fnmatch_translate(pat: bytes) -> Tuple[bool, Pattern[bytes]]:
parts = []
seq: Optional[int] = None
i = 0
any_char = b"[^/]" if pat[0:1] == b"/":
parts.append(b"^")
pat = pat[1:] else: # By default match the entire path up to a / # but if / doesn't appear in the pattern we will mark is as # a name pattern and just produce a pattern that matches against # the filename
parts.append(b"^(?:.*/)?")
name_pattern = True if pat[-1:] == b"/": # If the last character is / match this directory or any subdirectory
pat = pat[:-1]
suffix = b"(?:/|$)" else:
suffix = b"$" while i < len(pat):
c = pat[i:i+1] if c == b"\\": if i < len(pat) - 1:
i += 1
c = pat[i:i+1]
parts.append(re.escape(c)) else: raise ValueError elif seq isnotNone: # TODO: this doesn't really handle invalid sequences in the right way if c == b"]":
seq = None if parts[-1] == b"[":
parts = parts[:-1] elif parts[-1] == b"^"and parts[-2] == b"[": raise ValueError else:
parts.append(c) elif c == b"-":
parts.append(c) elif c == b"[": raise ValueError else:
parts.append(re.escape(c)) elif c == b"[":
parts.append(b"[") if i < len(pat) - 1 and pat[i+1:i+2] in (b"!", b"^"):
parts.append(b"^")
i += 1
seq = i elif c == b"*": if i < len(pat) - 1 and pat[i+1:i+2] == b"*": if i > 0 and pat[i-1:i] != b"/": raise ValueError
parts.append(b".*")
i += 1 if i < len(pat) - 1 and pat[i+1:i+2] != b"/": raise ValueError else:
parts.append(any_char + b"*") elif c == b"?":
parts.append(any_char) elif c == b"/"andnot seq:
name_pattern = False
parts.append(c) else:
parts.append(re.escape(c))
i += 1
# Regexp matching rules that have to be converted to patterns
pattern_re = re.compile(br".*[\*\[\?]")
def parse_line(line: bytes) -> Optional[Tuple[bool, bool, bool, Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]]]]:
line = line.rstrip() ifnot line or line[0:1] == b"#": returnNone
invert = line[0:1] == b"!" if invert:
line = line[1:]
dir_only = line[-1:] == b"/"
if dir_only:
line = line[:-1]
# Could make a special case for **/foo, but we don't have any patterns like that ifnot invert andnot pattern_re.match(line):
literal = True
pattern: Union[Tuple[bytes, ...], Tuple[bool, Pattern[bytes]]] = tuple(line.rsplit(b"/", 1)) else:
pattern = fnmatch_translate(line)
literal = False
if ignore_path and os.path.exists(ignore_path):
args: Tuple[Optional[bytes], List[bytes]] = (ignore_path, extras) else:
args = None, extras
self._read_ignore(*args)
def _read_ignore(self, ignore_path: Optional[bytes], extras: List[bytes]) -> None: if ignore_path isnotNone: with open(ignore_path, "rb") as f: for line in f:
self._read_line(line) for line in extras:
self._read_line(line)
if invert: # For exclude rules, we attach the rules to all preceeding patterns, so # that we can match patterns out of order and check if they were later # overriden by an exclude rule assertnot literal
rule = cast(Tuple[bool, Pattern[bytes]], rule) ifnot dir_only:
rules_iter: Iterable[Tuple[Any, List[Tuple[bool, Pattern[bytes]]]]] = itertools.chain(
itertools.chain(*(item.items() for item in self.literals_dir.values())),
itertools.chain(*(item.items() for item in self.literals_file.values())),
self.patterns_dir,
self.patterns_file) else:
rules_iter = itertools.chain(
itertools.chain(*(item.items() for item in self.literals_dir.values())),
self.patterns_dir)
for iter_items, literals, patterns, target, suffix in [
(dirnames, self.literals_dir, self.patterns_dir, keep_dirs, b"/"),
(filenames, self.literals_file, self.patterns_file, keep_files, b"")]: for item in iter_items:
name = item[0] if dirpath:
path = b"%s/%s" % (dirpath, name) + suffix else:
path = name + suffix if path in self.cache: ifnot self.cache[path]:
target.append(item) continue for rule_dir in [None, dirpath if dirpath != b"."else b""]: if name in literals.get(rule_dir, empty):
exclude = literals[rule_dir][name] ifnot any(rule.match(name if name_only else path) for name_only, rule in exclude): # Skip this item
self.cache[path] = True break else: for (component_only, pattern), exclude in patterns: if component_only:
match = pattern.match(name) else:
match = pattern.match(path) if match: ifnot any(rule.match(name if name_only else path) for name_only, rule in exclude): # Skip this item
self.cache[path] = True break else:
self.cache[path] = False
target.append(item)
dirnames[:] = keep_dirs assertnot any(b".git" == name for name, _ in dirnames) yield orig_dirpath, dirnames, keep_files
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.