Coverage for .claude/hooks/moai/lib/gitignore_parser.py: 0.00%
63 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-19 08:00 +0900
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-19 08:00 +0900
1#!/usr/bin/env python3
2""".gitignore parser utility
4Parse .gitignore files to extract patterns for exclusion.
6Features:
7- Parse .gitignore files
8- Exclude comments and empty lines
9- Normalize glob patterns
10- Handle directory patterns
12Usage:
13 from .utils.gitignore_parser import load_gitignore_patterns
14 patterns = load_gitignore_patterns()
15"""
17from pathlib import Path
18from typing import List, Set
21def load_gitignore_patterns(gitignore_path: str = ".gitignore") -> List[str]:
22 """Load patterns from .gitignore
24 Args:
25 gitignore_path: Path to .gitignore file (default: ".gitignore")
27 Returns:
28 List of exclude patterns
29 """
30 patterns = []
32 try:
33 gitignore_file = Path(gitignore_path)
34 if not gitignore_file.exists():
35 return patterns
37 with open(gitignore_file, 'r', encoding='utf-8') as f:
38 for line in f:
39 line = line.strip()
41 # Exclude empty lines or comments
42 if not line or line.startswith('#'):
43 continue
45 # Exclude negation patterns (!) - not used in validation
46 if line.startswith('!'):
47 continue
49 # Normalize pattern
50 pattern = normalize_pattern(line)
51 if pattern:
52 patterns.append(pattern)
54 except Exception:
55 # Return empty list on file read failure
56 pass
58 return patterns
61def normalize_pattern(pattern: str) -> str:
62 """Normalize pattern
64 Args:
65 pattern: Original pattern
67 Returns:
68 Normalized pattern
69 """
70 # Remove leading and trailing whitespace
71 pattern = pattern.strip()
73 # Absolute path pattern (starts with /)
74 if pattern.startswith('/'):
75 # Remove / and return
76 return pattern[1:]
78 # Return regular pattern as-is (including directory slashes)
79 return pattern
82def is_path_ignored(
83 file_path: str,
84 ignore_patterns: List[str],
85 protected_paths: List[str] = None
86) -> bool:
87 """Check if file path matches ignore patterns
89 Args:
90 file_path: File path to check
91 ignore_patterns: List of ignore patterns
92 protected_paths: Paths that should be protected (not ignored)
94 Returns:
95 True if matched
96 """
97 import fnmatch
99 # Set default protected paths
100 if protected_paths is None:
101 protected_paths = [".moai/specs/"]
103 # Do not ignore if protected path
104 for protected in protected_paths:
105 if file_path.startswith(protected):
106 return False
108 # Normalize path
109 path_parts = Path(file_path).parts
111 for pattern in ignore_patterns:
112 # Wildcard directory patterns (hooks_backup_*/, *_backup_*/)
113 if '*' in pattern and pattern.endswith('/'):
114 pattern_without_slash = pattern[:-1]
115 # Match with each path part
116 for part in path_parts:
117 if fnmatch.fnmatch(part, pattern_without_slash):
118 return True
120 # Wildcard pattern matching (*.ext, *backup*)
121 elif '*' in pattern:
122 # Match full path
123 if fnmatch.fnmatch(file_path, pattern):
124 return True
125 # Match each path part
126 for part in path_parts:
127 if fnmatch.fnmatch(part, pattern):
128 return True
130 # Directory pattern matching
131 elif pattern.endswith('/'):
132 dir_name = pattern[:-1]
133 if dir_name in path_parts:
134 return True
136 # Simple string matching (filename or directory name)
137 else:
138 if pattern in file_path:
139 return True
141 return False
144def get_combined_exclude_patterns(
145 base_patterns: List[str],
146 gitignore_path: str = ".gitignore"
147) -> List[str]:
148 """Combine base patterns with .gitignore patterns
150 Args:
151 base_patterns: Base exclude patterns
152 gitignore_path: Path to .gitignore file
154 Returns:
155 Combined exclude pattern list (deduplicated)
156 """
157 # Start with base patterns
158 patterns_set: Set[str] = set(base_patterns)
160 # Add .gitignore patterns
161 gitignore_patterns = load_gitignore_patterns(gitignore_path)
162 patterns_set.update(gitignore_patterns)
164 # Sort and return
165 return sorted(list(patterns_set))
168if __name__ == "__main__":
169 # Test code
170 patterns = load_gitignore_patterns()
171 print(f"Loaded {len(patterns)} patterns from .gitignore")
172 for pattern in patterns[:10]:
173 print(f" - {pattern}")