|
| 1 | +import logging |
| 2 | +import os |
| 3 | +from pathlib import Path |
| 4 | +from typing import List, Dict, Tuple |
| 5 | + |
| 6 | +import pathspec |
| 7 | + |
| 8 | + |
| 9 | +def read_ignore_file(file_path: Path) -> List[str]: |
| 10 | + """Read the ignore patterns from the specified ignore file.""" |
| 11 | + ignore_patterns = [] |
| 12 | + if file_path.is_file(): |
| 13 | + with file_path.open('r') as f: |
| 14 | + ignore_patterns = [line.strip() for line in f |
| 15 | + if line.strip() and not line.startswith('#')] |
| 16 | + logging.info(f"Using ignore patterns from {file_path}") |
| 17 | + logging.debug(f"Read ignore patterns from {file_path}: {ignore_patterns}") |
| 18 | + return ignore_patterns |
| 19 | + |
| 20 | + |
| 21 | +def load_pathspec(patterns: List[str], syntax='gitwildmatch') -> pathspec.PathSpec: |
| 22 | + """Load pathspec from a list of patterns.""" |
| 23 | + spec = pathspec.PathSpec.from_lines(syntax, patterns) |
| 24 | + logging.debug(f"Loaded pathspec with patterns: {patterns}") |
| 25 | + return spec |
| 26 | + |
| 27 | + |
| 28 | +def get_ignore_specs( |
| 29 | + root_dir: Path, |
| 30 | + custom_ignore_file: Path = None, |
| 31 | + no_default_ignores: bool = False, |
| 32 | + output_file: Path = None |
| 33 | +) -> Tuple[pathspec.PathSpec, Dict[Path, pathspec.PathSpec]]: |
| 34 | + """Get combined ignore specs and git ignore specs.""" |
| 35 | + default_patterns = get_default_patterns(root_dir, no_default_ignores, output_file) |
| 36 | + custom_patterns = get_custom_patterns(root_dir, custom_ignore_file) |
| 37 | + combined_patterns = custom_patterns if no_default_ignores else default_patterns + custom_patterns |
| 38 | + combined_spec = load_pathspec(combined_patterns) |
| 39 | + gitignore_specs = get_gitignore_specs(root_dir, no_default_ignores) |
| 40 | + |
| 41 | + return combined_spec, gitignore_specs |
| 42 | + |
| 43 | +def get_default_patterns(root_dir: Path, no_default_ignores: bool, output_file: Path) -> List[str]: |
| 44 | + """Retrieve default ignore patterns.""" |
| 45 | + if no_default_ignores: |
| 46 | + return [] |
| 47 | + |
| 48 | + patterns = [] |
| 49 | + # Add .treemapperignore patterns |
| 50 | + treemapper_ignore_file = root_dir / ".treemapperignore" |
| 51 | + patterns.extend(read_ignore_file(treemapper_ignore_file)) |
| 52 | + |
| 53 | + # Add default git patterns |
| 54 | + patterns.extend([".git/", ".git/**"]) |
| 55 | + |
| 56 | + # Add the output file to ignore patterns |
| 57 | + if output_file: |
| 58 | + try: |
| 59 | + relative_output = output_file.resolve().relative_to(root_dir.resolve()) |
| 60 | + patterns.append(str(relative_output)) |
| 61 | + if str(relative_output.parent) != ".": |
| 62 | + patterns.append(str(relative_output.parent) + "/") |
| 63 | + except ValueError: |
| 64 | + pass # Output file is outside root_dir; no need to add to ignores |
| 65 | + |
| 66 | + return patterns |
| 67 | + |
| 68 | +def get_custom_patterns(root_dir: Path, custom_ignore_file: Path) -> List[str]: |
| 69 | + """Retrieve custom ignore patterns.""" |
| 70 | + if not custom_ignore_file: |
| 71 | + return [] |
| 72 | + |
| 73 | + custom_ignore_file = custom_ignore_file if custom_ignore_file.is_absolute() else root_dir / custom_ignore_file |
| 74 | + if custom_ignore_file.is_file(): |
| 75 | + return read_ignore_file(custom_ignore_file) |
| 76 | + |
| 77 | + logging.warning(f"Custom ignore file '{custom_ignore_file}' not found.") |
| 78 | + return [] |
| 79 | + |
| 80 | +def get_gitignore_specs(root_dir: Path, no_default_ignores: bool) -> Dict[Path, pathspec.PathSpec]: |
| 81 | + """Retrieve gitignore specs for all .gitignore files in the directory.""" |
| 82 | + if no_default_ignores: |
| 83 | + return {} |
| 84 | + |
| 85 | + gitignore_specs = {} |
| 86 | + for dirpath, _, filenames in os.walk(root_dir): |
| 87 | + if ".gitignore" in filenames: |
| 88 | + gitignore_path = Path(dirpath) / ".gitignore" |
| 89 | + patterns = read_ignore_file(gitignore_path) |
| 90 | + gitignore_specs[Path(dirpath)] = load_pathspec(patterns) |
| 91 | + |
| 92 | + return gitignore_specs |
| 93 | + |
| 94 | + |
| 95 | + |
| 96 | +def should_ignore(file_path: str, combined_spec: pathspec.PathSpec) -> bool: |
| 97 | + """Check if a file or directory should be ignored based on combined pathspec.""" |
| 98 | + paths_to_check = [file_path] |
| 99 | + |
| 100 | + # Add path variations for checking |
| 101 | + if file_path.endswith('/'): |
| 102 | + paths_to_check.append(file_path) |
| 103 | + |
| 104 | + # Add parent directories with trailing slash |
| 105 | + for part in Path(file_path).parents: |
| 106 | + if part != Path('.'): |
| 107 | + paths_to_check.append(part.as_posix() + '/') |
| 108 | + |
| 109 | + result = any(combined_spec.match_file(path) for path in paths_to_check) |
| 110 | + logging.debug( |
| 111 | + f"Should ignore '{file_path}': {result} (checking paths: {paths_to_check})") |
| 112 | + return result |
0 commit comments