|
4 | 4 | """ |
5 | 5 |
|
6 | 6 | import os |
| 7 | +import sys |
7 | 8 | import fnmatch |
8 | 9 | from pathlib import Path |
9 | 10 | from typing import List, Set, Optional |
|
12 | 13 | _ignore_patterns: Optional[List[str]] = None |
13 | 14 | _ignore_patterns_file_mtime: Optional[float] = None |
14 | 15 | _ignore_patterns_file_path: Optional[Path] = None |
| 16 | +_bundled_patterns: Optional[List[str]] = None |
15 | 17 |
|
16 | 18 | IGNORE_FILE_NAME = ".dmcodeignore" |
17 | 19 |
|
18 | | -# Default patterns to always ignore (even without .dmcodeignore file) |
19 | | -DEFAULT_IGNORE_PATTERNS = [ |
20 | | - "node_modules", |
21 | | - "__pycache__", |
22 | | - ".git", |
23 | | - ".venv", |
24 | | - "venv", |
25 | | - ".env", |
26 | | - "dist", |
27 | | - "build", |
28 | | - ".idea", |
29 | | - ".vscode", |
30 | | - "*.pyc", |
31 | | - "*.pyo", |
32 | | -] |
33 | | - |
34 | | - |
35 | | -def _find_ignore_file(start_path: Optional[Path] = None) -> Optional[Path]: |
| 20 | + |
| 21 | +def _get_bundled_base_path() -> Optional[Path]: |
| 22 | + """Get the base path for bundled files (PyInstaller)""" |
| 23 | + # Check if running from PyInstaller bundle |
| 24 | + if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): |
| 25 | + return Path(sys._MEIPASS) |
| 26 | + return None |
| 27 | + |
| 28 | + |
| 29 | +def _load_bundled_patterns() -> List[str]: |
| 30 | + """Load patterns from bundled .dmcodeignore (inside the executable)""" |
| 31 | + global _bundled_patterns |
| 32 | + |
| 33 | + if _bundled_patterns is not None: |
| 34 | + return _bundled_patterns |
| 35 | + |
| 36 | + _bundled_patterns = [] |
| 37 | + bundled_path = _get_bundled_base_path() |
| 38 | + |
| 39 | + if bundled_path: |
| 40 | + ignore_file = bundled_path / IGNORE_FILE_NAME |
| 41 | + if ignore_file.exists(): |
| 42 | + try: |
| 43 | + with open(ignore_file, "r", encoding="utf-8") as f: |
| 44 | + for line in f: |
| 45 | + line = line.strip() |
| 46 | + if not line or line.startswith("#"): |
| 47 | + continue |
| 48 | + _bundled_patterns.append(line) |
| 49 | + except Exception: |
| 50 | + pass |
| 51 | + |
| 52 | + return _bundled_patterns |
| 53 | + |
| 54 | + |
| 55 | +def _find_project_ignore_file(start_path: Optional[Path] = None) -> Optional[Path]: |
36 | 56 | """ |
37 | | - Find .dmcodeignore file by searching: |
| 57 | + Find project-specific .dmcodeignore file by searching: |
38 | 58 | 1. In the start_path (if provided) |
39 | 59 | 2. In current working directory |
40 | 60 | 3. Walking up the directory tree from cwd |
@@ -64,49 +84,49 @@ def _find_ignore_file(start_path: Optional[Path] = None) -> Optional[Path]: |
64 | 84 |
|
65 | 85 | def _load_ignore_patterns(force_reload: bool = False, search_path: Optional[Path] = None) -> List[str]: |
66 | 86 | """ |
67 | | - Load patterns from .dmcodeignore file. |
| 87 | + Load patterns from .dmcodeignore files. |
| 88 | + Merges bundled patterns (from executable) with project-specific patterns. |
68 | 89 | Caches the result and reloads if file was modified. |
69 | | - Falls back to default patterns if no file found. |
70 | 90 | """ |
71 | 91 | global _ignore_patterns, _ignore_patterns_file_mtime, _ignore_patterns_file_path |
72 | 92 |
|
73 | | - ignore_file = _find_ignore_file(search_path) |
| 93 | + project_ignore_file = _find_project_ignore_file(search_path) |
74 | 94 |
|
75 | | - # Check if we need to reload |
| 95 | + # Check if we need to reload project patterns |
76 | 96 | if not force_reload and _ignore_patterns is not None: |
77 | | - if ignore_file: |
| 97 | + if project_ignore_file: |
78 | 98 | # Check if same file and not modified |
79 | | - if ignore_file == _ignore_patterns_file_path: |
80 | | - current_mtime = ignore_file.stat().st_mtime |
| 99 | + if project_ignore_file == _ignore_patterns_file_path: |
| 100 | + current_mtime = project_ignore_file.stat().st_mtime |
81 | 101 | if current_mtime == _ignore_patterns_file_mtime: |
82 | 102 | return _ignore_patterns |
83 | 103 | elif _ignore_patterns_file_path is None: |
84 | | - # No file before and still no file - return cached (defaults) |
| 104 | + # No file before and still no file - return cached |
85 | 105 | return _ignore_patterns |
86 | 106 |
|
87 | | - # Load patterns from file |
88 | | - patterns = [] |
| 107 | + # Start with bundled patterns (from inside the executable) |
| 108 | + patterns = _load_bundled_patterns().copy() |
89 | 109 |
|
90 | | - if ignore_file and ignore_file.exists(): |
| 110 | + # Add project-specific patterns if found |
| 111 | + if project_ignore_file and project_ignore_file.exists(): |
91 | 112 | try: |
92 | | - with open(ignore_file, "r", encoding="utf-8") as f: |
| 113 | + with open(project_ignore_file, "r", encoding="utf-8") as f: |
93 | 114 | for line in f: |
94 | 115 | line = line.strip() |
95 | 116 | # Skip empty lines and comments |
96 | 117 | if not line or line.startswith("#"): |
97 | 118 | continue |
98 | | - patterns.append(line) |
99 | | - _ignore_patterns_file_mtime = ignore_file.stat().st_mtime |
100 | | - _ignore_patterns_file_path = ignore_file |
| 119 | + # Avoid duplicates |
| 120 | + if line not in patterns: |
| 121 | + patterns.append(line) |
| 122 | + _ignore_patterns_file_mtime = project_ignore_file.stat().st_mtime |
| 123 | + _ignore_patterns_file_path = project_ignore_file |
101 | 124 | except Exception: |
102 | 125 | _ignore_patterns_file_mtime = None |
103 | 126 | _ignore_patterns_file_path = None |
104 | | - patterns = DEFAULT_IGNORE_PATTERNS.copy() |
105 | 127 | else: |
106 | | - # No .dmcodeignore found, use defaults |
107 | 128 | _ignore_patterns_file_mtime = None |
108 | 129 | _ignore_patterns_file_path = None |
109 | | - patterns = DEFAULT_IGNORE_PATTERNS.copy() |
110 | 130 |
|
111 | 131 | _ignore_patterns = patterns |
112 | 132 | return patterns |
|
0 commit comments