filefilter 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {filefilter-0.2.3/src/filefilter.egg-info → filefilter-0.2.4}/PKG-INFO +41 -5
- {filefilter-0.2.3 → filefilter-0.2.4}/README.md +40 -4
- {filefilter-0.2.3 → filefilter-0.2.4}/pyproject.toml +1 -1
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter/__init__.py +1 -1
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter/collector.py +8 -4
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter/ruleset.py +1 -0
- {filefilter-0.2.3 → filefilter-0.2.4/src/filefilter.egg-info}/PKG-INFO +41 -5
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_inclusion_logic.py +62 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_ruleset.py +29 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/LICENSE +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/setup.cfg +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter/utilities.py +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter.egg-info/SOURCES.txt +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter.egg-info/dependency_links.txt +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter.egg-info/requires.txt +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/src/filefilter.egg-info/top_level.txt +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_comprehensive.py +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_matcher.py +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_readme_verification.py +0 -0
- {filefilter-0.2.3 → filefilter-0.2.4}/tests/test_utilities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: filefilter
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Filter files in a directory tree based on configurable glob rules.
|
|
5
5
|
Author-email: "Ioannis D (devcoons)" <support@devcoons.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -161,10 +161,10 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
161
161
|
- If the file extension matches `exclude.extensions` → **excluded immediately**.
|
|
162
162
|
- This step is not overridden by include patterns.
|
|
163
163
|
|
|
164
|
-
2️⃣ **Path excludes vs. override
|
|
164
|
+
2️⃣ **Path excludes vs. override patterns (`include.odirs`, `include.ofiles`)**
|
|
165
165
|
- If the file path matches `exclude.files`, or its parent directory matches `exclude.dirs`, the file is a candidate for exclusion.
|
|
166
166
|
- **`include.dirs` and `include.files` do not override excludes** — they only gate inclusion later.
|
|
167
|
-
- If a matching pattern in `include.odirs` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
167
|
+
- If a matching pattern in `include.odirs` or `include.ofiles` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
168
168
|
- Otherwise → **excluded**.
|
|
169
169
|
|
|
170
170
|
| Pattern | Relative specificity |
|
|
@@ -172,9 +172,11 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
172
172
|
| `**` | very low (broad) |
|
|
173
173
|
| `**/KLM/**` | higher |
|
|
174
174
|
| `**/KLM/ABC/**` | highest |
|
|
175
|
+
| `**/test_*.py` vs `**/test_keep.py` | ofile wins (more specific) |
|
|
175
176
|
|
|
176
177
|
> Use `include.dirs` for normal scoping (e.g. `**`, `src/**`).
|
|
177
|
-
> Use `include.odirs`
|
|
178
|
+
> Use `include.odirs` for directory carve-outs under `exclude.dirs` / `exclude.files`.
|
|
179
|
+
> Use `include.ofiles` for file carve-outs under `exclude.files` / `exclude.dirs`.
|
|
178
180
|
|
|
179
181
|
3️⃣ **Include-file fast path**
|
|
180
182
|
- If the file matches any `include.files` pattern → **included immediately**,
|
|
@@ -202,6 +204,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
202
204
|
"include": {
|
|
203
205
|
"dirs": [],
|
|
204
206
|
"odirs": [],
|
|
207
|
+
"ofiles": [],
|
|
205
208
|
"files": [],
|
|
206
209
|
"extensions": []
|
|
207
210
|
},
|
|
@@ -219,6 +222,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
219
222
|
| `root_dir` | Base directory (absolute or relative). |
|
|
220
223
|
| `filters.include.dirs` | Directory inclusion patterns for gating (must match when any include filters are set). Never overrides excludes. |
|
|
221
224
|
| `filters.include.odirs` | Optional. Override directory patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies dir gating. |
|
|
225
|
+
| `filters.include.ofiles` | Optional. Override file patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies file gating (does not skip extension whitelist). |
|
|
222
226
|
| `filters.include.files` | File inclusion patterns (glob-like). Forces inclusion (skips extension whitelist) once path excludes are resolved. Does not override excludes. |
|
|
223
227
|
| `filters.include.extensions` | Extension whitelist. |
|
|
224
228
|
| `filters.exclude.*` | Same structure, but acts as exclusion filters. |
|
|
@@ -470,11 +474,43 @@ The same technique applies to other “exclude a path part, except one branch”
|
|
|
470
474
|
|
|
471
475
|
---
|
|
472
476
|
|
|
477
|
+
### Example 8 — Exclude file patterns, except specific files
|
|
478
|
+
|
|
479
|
+
Exclude all `test_*.py` files, but keep `test_keep.py`:
|
|
480
|
+
|
|
481
|
+
```json
|
|
482
|
+
{
|
|
483
|
+
"root_dir": ".",
|
|
484
|
+
"filters": {
|
|
485
|
+
"include": {
|
|
486
|
+
"dirs": ["**"],
|
|
487
|
+
"ofiles": ["**/test_keep.py"],
|
|
488
|
+
"files": [],
|
|
489
|
+
"extensions": ["py"]
|
|
490
|
+
},
|
|
491
|
+
"exclude": {
|
|
492
|
+
"dirs": [],
|
|
493
|
+
"files": ["**/test_*.py"],
|
|
494
|
+
"extensions": []
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
| File Path | Result | Reason |
|
|
501
|
+
|------------|---------|--------|
|
|
502
|
+
| `src/app.py` | ✅ | broad include + `.py` extension |
|
|
503
|
+
| `src/test_foo.py` | ❌ | `**/test_*.py` exclude; no matching `ofiles` |
|
|
504
|
+
| `src/test_keep.py` | ✅ | `**/test_keep.py` ofile beats `**/test_*.py` exclude |
|
|
505
|
+
| `nested/test_keep.py` | ✅ | ofile exception works at any depth |
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
473
509
|
## Summary of Behavior
|
|
474
510
|
|
|
475
511
|
- `exclude.extensions` are always applied first (hard exclude).
|
|
476
512
|
- `include.dirs` and `include.files` **never** override path excludes — they only gate inclusion.
|
|
477
|
-
- Only `include.odirs` can override a matching exclude, and only when the
|
|
513
|
+
- Only `include.odirs` and `include.ofiles` can override a matching exclude, and only when the override pattern is **more specific**.
|
|
478
514
|
- A matching `include.files` pattern forces inclusion (skips extension whitelist), once path excludes are resolved.
|
|
479
515
|
- If any include filters exist, at least one must match (inclusion gating).
|
|
480
516
|
- Extensions act as a **final whitelist** when not bypassed by `include.files`.
|
|
@@ -128,10 +128,10 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
128
128
|
- If the file extension matches `exclude.extensions` → **excluded immediately**.
|
|
129
129
|
- This step is not overridden by include patterns.
|
|
130
130
|
|
|
131
|
-
2️⃣ **Path excludes vs. override
|
|
131
|
+
2️⃣ **Path excludes vs. override patterns (`include.odirs`, `include.ofiles`)**
|
|
132
132
|
- If the file path matches `exclude.files`, or its parent directory matches `exclude.dirs`, the file is a candidate for exclusion.
|
|
133
133
|
- **`include.dirs` and `include.files` do not override excludes** — they only gate inclusion later.
|
|
134
|
-
- If a matching pattern in `include.odirs` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
134
|
+
- If a matching pattern in `include.odirs` or `include.ofiles` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
135
135
|
- Otherwise → **excluded**.
|
|
136
136
|
|
|
137
137
|
| Pattern | Relative specificity |
|
|
@@ -139,9 +139,11 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
139
139
|
| `**` | very low (broad) |
|
|
140
140
|
| `**/KLM/**` | higher |
|
|
141
141
|
| `**/KLM/ABC/**` | highest |
|
|
142
|
+
| `**/test_*.py` vs `**/test_keep.py` | ofile wins (more specific) |
|
|
142
143
|
|
|
143
144
|
> Use `include.dirs` for normal scoping (e.g. `**`, `src/**`).
|
|
144
|
-
> Use `include.odirs`
|
|
145
|
+
> Use `include.odirs` for directory carve-outs under `exclude.dirs` / `exclude.files`.
|
|
146
|
+
> Use `include.ofiles` for file carve-outs under `exclude.files` / `exclude.dirs`.
|
|
145
147
|
|
|
146
148
|
3️⃣ **Include-file fast path**
|
|
147
149
|
- If the file matches any `include.files` pattern → **included immediately**,
|
|
@@ -169,6 +171,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
169
171
|
"include": {
|
|
170
172
|
"dirs": [],
|
|
171
173
|
"odirs": [],
|
|
174
|
+
"ofiles": [],
|
|
172
175
|
"files": [],
|
|
173
176
|
"extensions": []
|
|
174
177
|
},
|
|
@@ -186,6 +189,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
186
189
|
| `root_dir` | Base directory (absolute or relative). |
|
|
187
190
|
| `filters.include.dirs` | Directory inclusion patterns for gating (must match when any include filters are set). Never overrides excludes. |
|
|
188
191
|
| `filters.include.odirs` | Optional. Override directory patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies dir gating. |
|
|
192
|
+
| `filters.include.ofiles` | Optional. Override file patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies file gating (does not skip extension whitelist). |
|
|
189
193
|
| `filters.include.files` | File inclusion patterns (glob-like). Forces inclusion (skips extension whitelist) once path excludes are resolved. Does not override excludes. |
|
|
190
194
|
| `filters.include.extensions` | Extension whitelist. |
|
|
191
195
|
| `filters.exclude.*` | Same structure, but acts as exclusion filters. |
|
|
@@ -437,11 +441,43 @@ The same technique applies to other “exclude a path part, except one branch”
|
|
|
437
441
|
|
|
438
442
|
---
|
|
439
443
|
|
|
444
|
+
### Example 8 — Exclude file patterns, except specific files
|
|
445
|
+
|
|
446
|
+
Exclude all `test_*.py` files, but keep `test_keep.py`:
|
|
447
|
+
|
|
448
|
+
```json
|
|
449
|
+
{
|
|
450
|
+
"root_dir": ".",
|
|
451
|
+
"filters": {
|
|
452
|
+
"include": {
|
|
453
|
+
"dirs": ["**"],
|
|
454
|
+
"ofiles": ["**/test_keep.py"],
|
|
455
|
+
"files": [],
|
|
456
|
+
"extensions": ["py"]
|
|
457
|
+
},
|
|
458
|
+
"exclude": {
|
|
459
|
+
"dirs": [],
|
|
460
|
+
"files": ["**/test_*.py"],
|
|
461
|
+
"extensions": []
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
| File Path | Result | Reason |
|
|
468
|
+
|------------|---------|--------|
|
|
469
|
+
| `src/app.py` | ✅ | broad include + `.py` extension |
|
|
470
|
+
| `src/test_foo.py` | ❌ | `**/test_*.py` exclude; no matching `ofiles` |
|
|
471
|
+
| `src/test_keep.py` | ✅ | `**/test_keep.py` ofile beats `**/test_*.py` exclude |
|
|
472
|
+
| `nested/test_keep.py` | ✅ | ofile exception works at any depth |
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
440
476
|
## Summary of Behavior
|
|
441
477
|
|
|
442
478
|
- `exclude.extensions` are always applied first (hard exclude).
|
|
443
479
|
- `include.dirs` and `include.files` **never** override path excludes — they only gate inclusion.
|
|
444
|
-
- Only `include.odirs` can override a matching exclude, and only when the
|
|
480
|
+
- Only `include.odirs` and `include.ofiles` can override a matching exclude, and only when the override pattern is **more specific**.
|
|
445
481
|
- A matching `include.files` pattern forces inclusion (skips extension whitelist), once path excludes are resolved.
|
|
446
482
|
- If any include filters exist, at least one must match (inclusion gating).
|
|
447
483
|
- Extensions act as a **final whitelist** when not bypassed by `include.files`.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "filefilter"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Filter files in a directory tree based on configurable glob rules."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Ioannis D (devcoons)", email = "support@devcoons.com" }]
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
# #
|
|
25
25
|
#########################################################################################
|
|
26
26
|
|
|
27
|
-
__version__ = '0.2.
|
|
27
|
+
__version__ = '0.2.4'
|
|
28
28
|
|
|
29
29
|
#########################################################################################
|
|
30
30
|
# IMPORTS #
|
|
@@ -168,6 +168,9 @@ def should_include(full_path: str, cfg: Ruleset) -> bool:
|
|
|
168
168
|
gate_dirs = _merge(inc_dirs, odirs)
|
|
169
169
|
|
|
170
170
|
odir_spec = best_matching_specificity(odirs, match_dir, dir_rel)
|
|
171
|
+
ofiles = cfg.include_ofiles or []
|
|
172
|
+
ofile_spec = best_matching_specificity(ofiles, match_file, rel)
|
|
173
|
+
override_spec = max(odir_spec, ofile_spec)
|
|
171
174
|
|
|
172
175
|
exc_dirs = _merge(cfg.exc_dirs_root, cfg.exc_dirs_one, cfg.exc_dirs_any)
|
|
173
176
|
exc_path_spec = -1
|
|
@@ -184,13 +187,14 @@ def should_include(full_path: str, cfg: Ruleset) -> bool:
|
|
|
184
187
|
exc_path_spec,
|
|
185
188
|
best_matching_specificity(exc_dirs, match_dir, dir_rel),
|
|
186
189
|
)
|
|
187
|
-
if exclude_path_hit and
|
|
190
|
+
if exclude_path_hit and override_spec <= exc_path_spec:
|
|
188
191
|
return False
|
|
189
192
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if files_match:
|
|
193
|
+
gate_files = _merge(cfg.include_files, ofiles)
|
|
194
|
+
if cfg.include_files and match_file(rel, cfg.include_files):
|
|
193
195
|
return True
|
|
196
|
+
files_present = bool(gate_files)
|
|
197
|
+
files_match = files_present and match_file(rel, gate_files)
|
|
194
198
|
dirs_present = bool(gate_dirs)
|
|
195
199
|
dirs_match = dirs_present and match_dir(dir_rel, gate_dirs)
|
|
196
200
|
if (files_present or dirs_present) and not (files_match or dirs_match):
|
|
@@ -58,6 +58,7 @@ class Ruleset:
|
|
|
58
58
|
self.exc_dirs_root, self.exc_dirs_one, self.exc_dirs_any = parse_dir_patterns(exc.get('dirs', []))
|
|
59
59
|
|
|
60
60
|
self.include_files = parse_file_patterns(inc.get('files', []))
|
|
61
|
+
self.include_ofiles = parse_file_patterns(inc.get('ofiles', []))
|
|
61
62
|
self.exclude_files = parse_file_patterns(exc.get('files', []))
|
|
62
63
|
|
|
63
64
|
self.inc_exts = parse_extensions(inc.get('extensions', []))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: filefilter
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Filter files in a directory tree based on configurable glob rules.
|
|
5
5
|
Author-email: "Ioannis D (devcoons)" <support@devcoons.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -161,10 +161,10 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
161
161
|
- If the file extension matches `exclude.extensions` → **excluded immediately**.
|
|
162
162
|
- This step is not overridden by include patterns.
|
|
163
163
|
|
|
164
|
-
2️⃣ **Path excludes vs. override
|
|
164
|
+
2️⃣ **Path excludes vs. override patterns (`include.odirs`, `include.ofiles`)**
|
|
165
165
|
- If the file path matches `exclude.files`, or its parent directory matches `exclude.dirs`, the file is a candidate for exclusion.
|
|
166
166
|
- **`include.dirs` and `include.files` do not override excludes** — they only gate inclusion later.
|
|
167
|
-
- If a matching pattern in `include.odirs` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
167
|
+
- If a matching pattern in `include.odirs` or `include.ofiles` is **more specific** than the best matching exclude pattern → the exclude is **ignored** for this file.
|
|
168
168
|
- Otherwise → **excluded**.
|
|
169
169
|
|
|
170
170
|
| Pattern | Relative specificity |
|
|
@@ -172,9 +172,11 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
172
172
|
| `**` | very low (broad) |
|
|
173
173
|
| `**/KLM/**` | higher |
|
|
174
174
|
| `**/KLM/ABC/**` | highest |
|
|
175
|
+
| `**/test_*.py` vs `**/test_keep.py` | ofile wins (more specific) |
|
|
175
176
|
|
|
176
177
|
> Use `include.dirs` for normal scoping (e.g. `**`, `src/**`).
|
|
177
|
-
> Use `include.odirs`
|
|
178
|
+
> Use `include.odirs` for directory carve-outs under `exclude.dirs` / `exclude.files`.
|
|
179
|
+
> Use `include.ofiles` for file carve-outs under `exclude.files` / `exclude.dirs`.
|
|
178
180
|
|
|
179
181
|
3️⃣ **Include-file fast path**
|
|
180
182
|
- If the file matches any `include.files` pattern → **included immediately**,
|
|
@@ -202,6 +204,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
202
204
|
"include": {
|
|
203
205
|
"dirs": [],
|
|
204
206
|
"odirs": [],
|
|
207
|
+
"ofiles": [],
|
|
205
208
|
"files": [],
|
|
206
209
|
"extensions": []
|
|
207
210
|
},
|
|
@@ -219,6 +222,7 @@ When scanning files, the library applies filters in this exact sequence:
|
|
|
219
222
|
| `root_dir` | Base directory (absolute or relative). |
|
|
220
223
|
| `filters.include.dirs` | Directory inclusion patterns for gating (must match when any include filters are set). Never overrides excludes. |
|
|
221
224
|
| `filters.include.odirs` | Optional. Override directory patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies dir gating. |
|
|
225
|
+
| `filters.include.ofiles` | Optional. Override file patterns (defaults to `[]` when omitted). When more specific than a matching exclude, the exclude is ignored for that path. Also satisfies file gating (does not skip extension whitelist). |
|
|
222
226
|
| `filters.include.files` | File inclusion patterns (glob-like). Forces inclusion (skips extension whitelist) once path excludes are resolved. Does not override excludes. |
|
|
223
227
|
| `filters.include.extensions` | Extension whitelist. |
|
|
224
228
|
| `filters.exclude.*` | Same structure, but acts as exclusion filters. |
|
|
@@ -470,11 +474,43 @@ The same technique applies to other “exclude a path part, except one branch”
|
|
|
470
474
|
|
|
471
475
|
---
|
|
472
476
|
|
|
477
|
+
### Example 8 — Exclude file patterns, except specific files
|
|
478
|
+
|
|
479
|
+
Exclude all `test_*.py` files, but keep `test_keep.py`:
|
|
480
|
+
|
|
481
|
+
```json
|
|
482
|
+
{
|
|
483
|
+
"root_dir": ".",
|
|
484
|
+
"filters": {
|
|
485
|
+
"include": {
|
|
486
|
+
"dirs": ["**"],
|
|
487
|
+
"ofiles": ["**/test_keep.py"],
|
|
488
|
+
"files": [],
|
|
489
|
+
"extensions": ["py"]
|
|
490
|
+
},
|
|
491
|
+
"exclude": {
|
|
492
|
+
"dirs": [],
|
|
493
|
+
"files": ["**/test_*.py"],
|
|
494
|
+
"extensions": []
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
| File Path | Result | Reason |
|
|
501
|
+
|------------|---------|--------|
|
|
502
|
+
| `src/app.py` | ✅ | broad include + `.py` extension |
|
|
503
|
+
| `src/test_foo.py` | ❌ | `**/test_*.py` exclude; no matching `ofiles` |
|
|
504
|
+
| `src/test_keep.py` | ✅ | `**/test_keep.py` ofile beats `**/test_*.py` exclude |
|
|
505
|
+
| `nested/test_keep.py` | ✅ | ofile exception works at any depth |
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
473
509
|
## Summary of Behavior
|
|
474
510
|
|
|
475
511
|
- `exclude.extensions` are always applied first (hard exclude).
|
|
476
512
|
- `include.dirs` and `include.files` **never** override path excludes — they only gate inclusion.
|
|
477
|
-
- Only `include.odirs` can override a matching exclude, and only when the
|
|
513
|
+
- Only `include.odirs` and `include.ofiles` can override a matching exclude, and only when the override pattern is **more specific**.
|
|
478
514
|
- A matching `include.files` pattern forces inclusion (skips extension whitelist), once path excludes are resolved.
|
|
479
515
|
- If any include filters exist, at least one must match (inclusion gating).
|
|
480
516
|
- Extensions act as a **final whitelist** when not bypassed by `include.files`.
|
|
@@ -250,3 +250,65 @@ def test_abc_klm_exception_via_include_odirs(tmp_path: Path):
|
|
|
250
250
|
assert str(tmp_path / "src" / "ABC" / "KLM" / "nested" / "keep.c") in paths
|
|
251
251
|
assert str(tmp_path / "src" / "x" / "ABC" / "other" / "skip.c") not in paths
|
|
252
252
|
assert str(tmp_path / "src" / "x" / "ABC" / "KLM" / "keep.c") in paths
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_specific_ofile_beats_broader_exclude_file(tmp_path: Path):
|
|
256
|
+
"""Only include.ofiles override excludes; include.files do not."""
|
|
257
|
+
touch(tmp_path / "src" / "app.py")
|
|
258
|
+
touch(tmp_path / "src" / "test_foo.py")
|
|
259
|
+
touch(tmp_path / "src" / "test_keep.py")
|
|
260
|
+
touch(tmp_path / "src" / "nested" / "test_keep.py")
|
|
261
|
+
|
|
262
|
+
cfg = make_config(
|
|
263
|
+
include_dirs=["**"],
|
|
264
|
+
include_ofiles=["**/test_keep.py"],
|
|
265
|
+
include_extensions=["py"],
|
|
266
|
+
exclude_files=["**/test_*.py"],
|
|
267
|
+
)
|
|
268
|
+
paths = select_paths(tmp_path, cfg)
|
|
269
|
+
|
|
270
|
+
assert str(tmp_path / "src" / "app.py") in paths
|
|
271
|
+
assert str(tmp_path / "src" / "test_foo.py") not in paths
|
|
272
|
+
assert str(tmp_path / "src" / "test_keep.py") in paths
|
|
273
|
+
assert str(tmp_path / "src" / "nested" / "test_keep.py") in paths
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_include_files_do_not_override_exclude_file(tmp_path: Path):
|
|
277
|
+
touch(tmp_path / "src" / "test_keep.py")
|
|
278
|
+
cfg = make_config(
|
|
279
|
+
include_dirs=["**"],
|
|
280
|
+
include_files=["**/test_keep.py", "**/test_*.py"],
|
|
281
|
+
include_extensions=["py"],
|
|
282
|
+
exclude_files=["**/test_*.py"],
|
|
283
|
+
)
|
|
284
|
+
paths = select_paths(tmp_path, cfg)
|
|
285
|
+
assert str(tmp_path / "src" / "test_keep.py") not in paths
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_ofile_overrides_exclude_dir_for_single_file(tmp_path: Path):
|
|
289
|
+
touch(tmp_path / "build" / "app.py")
|
|
290
|
+
touch(tmp_path / "build" / "keep.py")
|
|
291
|
+
cfg = make_config(
|
|
292
|
+
include_dirs=["**"],
|
|
293
|
+
include_ofiles=["**/build/keep.py"],
|
|
294
|
+
include_extensions=["py"],
|
|
295
|
+
exclude_dirs=["**/build/**"],
|
|
296
|
+
)
|
|
297
|
+
paths = select_paths(tmp_path, cfg)
|
|
298
|
+
assert str(tmp_path / "build" / "app.py") not in paths
|
|
299
|
+
assert str(tmp_path / "build" / "keep.py") in paths
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_ofile_satisfies_file_gating_without_fast_path(tmp_path: Path):
|
|
303
|
+
"""ofiles satisfy gating but do not skip extension whitelist."""
|
|
304
|
+
touch(tmp_path / "src" / "keep.txt")
|
|
305
|
+
touch(tmp_path / "src" / "skip.txt")
|
|
306
|
+
cfg = make_config(
|
|
307
|
+
include_files=["**/only.py"],
|
|
308
|
+
include_ofiles=["**/keep.txt"],
|
|
309
|
+
include_extensions=["py"],
|
|
310
|
+
exclude_files=["**/*.txt"],
|
|
311
|
+
)
|
|
312
|
+
paths = select_paths(tmp_path, cfg)
|
|
313
|
+
assert str(tmp_path / "src" / "keep.txt") not in paths
|
|
314
|
+
assert str(tmp_path / "src" / "skip.txt") not in paths
|
|
@@ -86,6 +86,35 @@ def test_parse_odirs_buckets_like_dirs():
|
|
|
86
86
|
assert rules.inc_odirs_one == ["*/pkg"]
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def test_missing_ofiles_defaults_to_empty(tmp_path: Path):
|
|
90
|
+
cfg = {
|
|
91
|
+
"root_dir": ".",
|
|
92
|
+
"filters": {
|
|
93
|
+
"include": {"dirs": ["**"], "extensions": ["py"]},
|
|
94
|
+
"exclude": {"files": ["**/skip.py"]},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
touch(tmp_path / "app.py")
|
|
98
|
+
touch(tmp_path / "skip.py")
|
|
99
|
+
rules = load(json.dumps(cfg), base=str(tmp_path))
|
|
100
|
+
assert rules.include_ofiles == []
|
|
101
|
+
assert matches(str(tmp_path / "app.py"), rules) is True
|
|
102
|
+
assert matches(str(tmp_path / "skip.py"), rules) is False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_parse_ofiles_like_files():
|
|
106
|
+
cfg = {
|
|
107
|
+
"root_dir": ".",
|
|
108
|
+
"filters": {
|
|
109
|
+
"include": {"files": ["*.py"], "ofiles": ["**/keep.txt", "README.*"]},
|
|
110
|
+
"exclude": {},
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
rules = Ruleset(cfg, resolve_base=".")
|
|
114
|
+
assert rules.include_files == ["*.py"]
|
|
115
|
+
assert rules.include_ofiles == ["**/keep.txt", "readme.*"]
|
|
116
|
+
|
|
117
|
+
|
|
89
118
|
def test_empty_dir_patterns_are_ignored(tmp_path: Path):
|
|
90
119
|
cfg = {
|
|
91
120
|
"root_dir": ".",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|