| 88 | return list(default_patterns) + user_patterns |
| 89 | |
| 90 | class CGCIgnoreMatcher: |
| 91 | def __init__(self, patterns: list[str], root_dir: Path): |
| 92 | self.root_dir = root_dir |
| 93 | self.rules = self._compile_patterns(patterns) |
| 94 | |
| 95 | def _translate_segment(self, seg: str) -> str: |
| 96 | """Translates a single gitwildmatch segment into regex, completely avoiding fnmatch.""" |
| 97 | i, n = 0, len(seg) |
| 98 | res = "" |
| 99 | while i < n: |
| 100 | c = seg[i] |
| 101 | i += 1 |
| 102 | if c == '*': |
| 103 | res += '[^/]*' |
| 104 | elif c == '?': |
| 105 | res += '[^/]' |
| 106 | elif c == '\\': |
| 107 | # Handle gitignore escape sequence (e.g. \! matches a literal !) |
| 108 | if i < n: |
| 109 | res += re.escape(seg[i]) |
| 110 | i += 1 |
| 111 | else: |
| 112 | res += re.escape('\\') |
| 113 | elif c == '[': |
| 114 | j = i |
| 115 | if j < n and seg[j] == '!': |
| 116 | j += 1 |
| 117 | if j < n and seg[j] == ']': |
| 118 | j += 1 |
| 119 | while j < n and seg[j] != ']': |
| 120 | j += 1 |
| 121 | if j >= n: |
| 122 | res += '\\[' |
| 123 | else: |
| 124 | stuff = seg[i:j].replace('\\', '\\\\') |
| 125 | i = j + 1 |
| 126 | if stuff[0] == '!': |
| 127 | stuff = '^' + stuff[1:] |
| 128 | elif stuff[0] == '^': |
| 129 | stuff = '\\' + stuff |
| 130 | res += '[' + stuff + ']' |
| 131 | else: |
| 132 | res += re.escape(c) |
| 133 | return res |
| 134 | |
| 135 | def _compile_patterns(self, patterns: list[str]) -> list[Tuple[bool, re.Pattern]]: |
| 136 | rules = [] |
| 137 | for raw_pat in patterns: |
| 138 | pat = raw_pat.strip() |
| 139 | if not pat or pat.startswith('#'): |
| 140 | continue |
| 141 | |
| 142 | is_negation = pat.startswith('!') |
| 143 | if is_negation: |
| 144 | pat = pat[1:] |
| 145 | |
| 146 | is_dir_only = pat.endswith('/') |
| 147 | if is_dir_only: |
no outgoing calls