pseek 3.6.0__tar.gz → 3.6.2__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.
- {pseek-3.6.0 → pseek-3.6.2}/PKG-INFO +29 -7
- {pseek-3.6.0 → pseek-3.6.2}/README.md +28 -6
- {pseek-3.6.0 → pseek-3.6.2}/pseek/archive.py +11 -17
- {pseek-3.6.0 → pseek-3.6.2}/pseek/parser.py +51 -14
- {pseek-3.6.0 → pseek-3.6.2}/pseek/searcher.py +45 -40
- {pseek-3.6.0 → pseek-3.6.2}/pseek/utils.py +9 -3
- {pseek-3.6.0 → pseek-3.6.2}/pyproject.toml +1 -1
- {pseek-3.6.0 → pseek-3.6.2}/.gitattributes +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/.github/workflows/publish.yml +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/.gitignore +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/Cargo.lock +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/Cargo.toml +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/LICENSE +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/pseek/__init__.py +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/pseek/cli.py +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/pseek/compound_extensions +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/pseek/structs.py +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/scripts/check_version.py +0 -0
- {pseek-3.6.0 → pseek-3.6.2}/src/lib.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pseek
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.2
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -62,6 +62,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
62
62
|
* [Case Sensitive](#case-sensitive)
|
|
63
63
|
* [Whole Word](#whole-word)
|
|
64
64
|
* [Fuzzy](#fuzzy)
|
|
65
|
+
* [Full Path](#full-path-1)
|
|
65
66
|
* [Combined Prefixes](#combined-prefixes)
|
|
66
67
|
* [Automatic Filtering](#automatic-filtering)
|
|
67
68
|
* [Manual Filtering](#manual-filtering)
|
|
@@ -100,17 +101,19 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
100
101
|
* Search file names
|
|
101
102
|
* Search directory names
|
|
102
103
|
* Search file contents
|
|
103
|
-
* Search inside archive files
|
|
104
104
|
* Boolean query expressions (`and`, `or`, `not`)
|
|
105
105
|
* Regular expressions
|
|
106
106
|
* Fuzzy matching
|
|
107
107
|
* Whole-word matching
|
|
108
108
|
* Case-sensitive search
|
|
109
|
+
* Search inside archive files
|
|
109
110
|
* Archive recursion depth control
|
|
110
111
|
* Extension filters
|
|
111
112
|
* Path filters
|
|
112
113
|
* Size filtering
|
|
113
|
-
*
|
|
114
|
+
* Depth filtering
|
|
115
|
+
* Automatic filtering (Supports git rules)
|
|
116
|
+
* Parallel search
|
|
114
117
|
* Highlight matches in output
|
|
115
118
|
* Cross-platform (Linux, macOS, Windows)
|
|
116
119
|
|
|
@@ -173,7 +176,7 @@ simultaneously.
|
|
|
173
176
|
To search for a query that starts with `-`, use `--` to mark the end of options:
|
|
174
177
|
|
|
175
178
|
```bash
|
|
176
|
-
psk -- --path
|
|
179
|
+
psk -- --path # The query is "--path"
|
|
177
180
|
```
|
|
178
181
|
|
|
179
182
|
Of course the search query is optional. When no query is provided, Pseek displays the files and directories and file contents within the selected scope.
|
|
@@ -289,7 +292,7 @@ Example:
|
|
|
289
292
|
psk "hello world"
|
|
290
293
|
```
|
|
291
294
|
|
|
292
|
-
> **Note:** To use case-sensitive, whole-word matching, regular expression search, and
|
|
295
|
+
> **Note:** To use case-sensitive, whole-word matching, regular expression search, fuzzy search, and full path search when `--expr` is enabled, we can use [expression prefixes](#expression-prefixes).
|
|
293
296
|
|
|
294
297
|
### Case Sensitive Search
|
|
295
298
|
|
|
@@ -316,11 +319,13 @@ Does not match: `cats`, `concatenate`
|
|
|
316
319
|
Enable regex mode:
|
|
317
320
|
|
|
318
321
|
```bash
|
|
319
|
-
psk error\d+ --regex
|
|
322
|
+
psk "error\d+" --regex
|
|
320
323
|
```
|
|
321
324
|
|
|
322
325
|
Example matches: `error1`, `error25`, `error999`
|
|
323
326
|
|
|
327
|
+
> **Note:** Regex matching and whole word matching cannot be used at the same time, because we can use `\b` in regex to enable whole word matching: `psk "\berror\d+\b" --regex`
|
|
328
|
+
|
|
324
329
|
### Fuzzy Search
|
|
325
330
|
|
|
326
331
|
Fuzzy search allows approximate matching.
|
|
@@ -444,6 +449,8 @@ Used for grouping expressions.
|
|
|
444
449
|
|
|
445
450
|
Each term can have its own search mode.
|
|
446
451
|
|
|
452
|
+
> **Note:** For more detailed information about the modes, please refer to the [query modes](#query-modes) section.
|
|
453
|
+
|
|
447
454
|
#### Regex
|
|
448
455
|
|
|
449
456
|
```text
|
|
@@ -492,6 +499,18 @@ Example:
|
|
|
492
499
|
psk 'f"apple"' --expr
|
|
493
500
|
```
|
|
494
501
|
|
|
502
|
+
#### Full Path
|
|
503
|
+
|
|
504
|
+
```text
|
|
505
|
+
p"text"
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Example:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
psk 'p"lib"' --expr
|
|
512
|
+
```
|
|
513
|
+
|
|
495
514
|
#### Combined Prefixes
|
|
496
515
|
|
|
497
516
|
Prefixes can be combined.
|
|
@@ -502,6 +521,7 @@ Examples:
|
|
|
502
521
|
rc"text"
|
|
503
522
|
cw"text"
|
|
504
523
|
cf"text"
|
|
524
|
+
pc"text"
|
|
505
525
|
wcf"text"
|
|
506
526
|
```
|
|
507
527
|
|
|
@@ -518,7 +538,9 @@ Meaning:
|
|
|
518
538
|
|
|
519
539
|
simultaneously.
|
|
520
540
|
|
|
521
|
-
Allowed modes: `r`, `c`, `w`, `f`, `
|
|
541
|
+
Allowed modes: `r`, `c`, `w`, `f`, `p`, `rp`, `pc`, `wp`, `fp`, `rc`, `cw`, `cf`, `wf`, `cwf`, `rcp`, `cwp`, `cfp`, `wfp`, `cwfp`
|
|
542
|
+
|
|
543
|
+
and their permutations.
|
|
522
544
|
|
|
523
545
|
> **Note:** Whole word matching and regex matching cannot be used at the same time, because we can use `\b` in regex to enable whole word matching: `r"\btext\b"`
|
|
524
546
|
|
|
@@ -34,6 +34,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
34
34
|
* [Case Sensitive](#case-sensitive)
|
|
35
35
|
* [Whole Word](#whole-word)
|
|
36
36
|
* [Fuzzy](#fuzzy)
|
|
37
|
+
* [Full Path](#full-path-1)
|
|
37
38
|
* [Combined Prefixes](#combined-prefixes)
|
|
38
39
|
* [Automatic Filtering](#automatic-filtering)
|
|
39
40
|
* [Manual Filtering](#manual-filtering)
|
|
@@ -72,17 +73,19 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
72
73
|
* Search file names
|
|
73
74
|
* Search directory names
|
|
74
75
|
* Search file contents
|
|
75
|
-
* Search inside archive files
|
|
76
76
|
* Boolean query expressions (`and`, `or`, `not`)
|
|
77
77
|
* Regular expressions
|
|
78
78
|
* Fuzzy matching
|
|
79
79
|
* Whole-word matching
|
|
80
80
|
* Case-sensitive search
|
|
81
|
+
* Search inside archive files
|
|
81
82
|
* Archive recursion depth control
|
|
82
83
|
* Extension filters
|
|
83
84
|
* Path filters
|
|
84
85
|
* Size filtering
|
|
85
|
-
*
|
|
86
|
+
* Depth filtering
|
|
87
|
+
* Automatic filtering (Supports git rules)
|
|
88
|
+
* Parallel search
|
|
86
89
|
* Highlight matches in output
|
|
87
90
|
* Cross-platform (Linux, macOS, Windows)
|
|
88
91
|
|
|
@@ -145,7 +148,7 @@ simultaneously.
|
|
|
145
148
|
To search for a query that starts with `-`, use `--` to mark the end of options:
|
|
146
149
|
|
|
147
150
|
```bash
|
|
148
|
-
psk -- --path
|
|
151
|
+
psk -- --path # The query is "--path"
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
Of course the search query is optional. When no query is provided, Pseek displays the files and directories and file contents within the selected scope.
|
|
@@ -261,7 +264,7 @@ Example:
|
|
|
261
264
|
psk "hello world"
|
|
262
265
|
```
|
|
263
266
|
|
|
264
|
-
> **Note:** To use case-sensitive, whole-word matching, regular expression search, and
|
|
267
|
+
> **Note:** To use case-sensitive, whole-word matching, regular expression search, fuzzy search, and full path search when `--expr` is enabled, we can use [expression prefixes](#expression-prefixes).
|
|
265
268
|
|
|
266
269
|
### Case Sensitive Search
|
|
267
270
|
|
|
@@ -288,11 +291,13 @@ Does not match: `cats`, `concatenate`
|
|
|
288
291
|
Enable regex mode:
|
|
289
292
|
|
|
290
293
|
```bash
|
|
291
|
-
psk error\d+ --regex
|
|
294
|
+
psk "error\d+" --regex
|
|
292
295
|
```
|
|
293
296
|
|
|
294
297
|
Example matches: `error1`, `error25`, `error999`
|
|
295
298
|
|
|
299
|
+
> **Note:** Regex matching and whole word matching cannot be used at the same time, because we can use `\b` in regex to enable whole word matching: `psk "\berror\d+\b" --regex`
|
|
300
|
+
|
|
296
301
|
### Fuzzy Search
|
|
297
302
|
|
|
298
303
|
Fuzzy search allows approximate matching.
|
|
@@ -416,6 +421,8 @@ Used for grouping expressions.
|
|
|
416
421
|
|
|
417
422
|
Each term can have its own search mode.
|
|
418
423
|
|
|
424
|
+
> **Note:** For more detailed information about the modes, please refer to the [query modes](#query-modes) section.
|
|
425
|
+
|
|
419
426
|
#### Regex
|
|
420
427
|
|
|
421
428
|
```text
|
|
@@ -464,6 +471,18 @@ Example:
|
|
|
464
471
|
psk 'f"apple"' --expr
|
|
465
472
|
```
|
|
466
473
|
|
|
474
|
+
#### Full Path
|
|
475
|
+
|
|
476
|
+
```text
|
|
477
|
+
p"text"
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Example:
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
psk 'p"lib"' --expr
|
|
484
|
+
```
|
|
485
|
+
|
|
467
486
|
#### Combined Prefixes
|
|
468
487
|
|
|
469
488
|
Prefixes can be combined.
|
|
@@ -474,6 +493,7 @@ Examples:
|
|
|
474
493
|
rc"text"
|
|
475
494
|
cw"text"
|
|
476
495
|
cf"text"
|
|
496
|
+
pc"text"
|
|
477
497
|
wcf"text"
|
|
478
498
|
```
|
|
479
499
|
|
|
@@ -490,7 +510,9 @@ Meaning:
|
|
|
490
510
|
|
|
491
511
|
simultaneously.
|
|
492
512
|
|
|
493
|
-
Allowed modes: `r`, `c`, `w`, `f`, `
|
|
513
|
+
Allowed modes: `r`, `c`, `w`, `f`, `p`, `rp`, `pc`, `wp`, `fp`, `rc`, `cw`, `cf`, `wf`, `cwf`, `rcp`, `cwp`, `cfp`, `wfp`, `cwfp`
|
|
514
|
+
|
|
515
|
+
and their permutations.
|
|
494
516
|
|
|
495
517
|
> **Note:** Whole word matching and regex matching cannot be used at the same time, because we can use `\b` in regex to enable whole word matching: `r"\btext\b"`
|
|
496
518
|
|
|
@@ -30,22 +30,16 @@ def get_archive_path_size(info, file_type: str) -> int:
|
|
|
30
30
|
def archive_should_skip(path_info: Path, config, p_size: float, file_ext, is_dir=False):
|
|
31
31
|
"""Check whether the file/directory inside archive files should be skipped based on various filters"""
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
path_info_str = str(path_info)
|
|
34
|
+
return (config.arc_include and not any(path_info.is_relative_to(inc) for inc in config.arc_include)) \
|
|
34
35
|
or (config.arc_exclude and any(path_info.is_relative_to(exc) for exc in config.arc_exclude)) \
|
|
35
36
|
or (config.arc_ext and file_ext not in config.arc_ext) \
|
|
36
37
|
or (config.arc_exc_ext and file_ext in config.arc_exc_ext) \
|
|
37
38
|
or (config.arc_size and (is_dir or not any(
|
|
38
39
|
minimum <= p_size <= maximum
|
|
39
40
|
for minimum, maximum in config.arc_size
|
|
40
|
-
)))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if config.re_include:
|
|
44
|
-
return not config.re_include.search(str(path_info))
|
|
45
|
-
if config.re_exclude:
|
|
46
|
-
return config.re_exclude.search(str(path_info)) is not None
|
|
47
|
-
|
|
48
|
-
return False
|
|
41
|
+
))) or (config.re_include and not config.re_include.search(path_info_str)) \
|
|
42
|
+
or (config.re_exclude and config.re_exclude.search(path_info_str) is not None)
|
|
49
43
|
|
|
50
44
|
|
|
51
45
|
def extract_names_from_archive(file_path: Path, config, depth: int = -1,
|
|
@@ -78,8 +72,8 @@ def extract_names_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
78
72
|
with opener(file_stream) as f:
|
|
79
73
|
for info in f.infolist():
|
|
80
74
|
name = Path(info.filename)
|
|
81
|
-
new_path_ext = get_path_suffix(name)
|
|
82
75
|
is_dir = info.is_dir()
|
|
76
|
+
new_path_ext = get_path_suffix(name, is_dir)
|
|
83
77
|
|
|
84
78
|
if not archive_should_skip(
|
|
85
79
|
name,
|
|
@@ -105,8 +99,8 @@ def extract_names_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
105
99
|
with py7zr.SevenZipFile(file_stream, mode='r') as z:
|
|
106
100
|
for info in z.list():
|
|
107
101
|
name = Path(info.filename)
|
|
108
|
-
new_path_ext = get_path_suffix(name)
|
|
109
102
|
is_dir = info.is_directory
|
|
103
|
+
new_path_ext = get_path_suffix(name, is_dir)
|
|
110
104
|
|
|
111
105
|
if not archive_should_skip(
|
|
112
106
|
name,
|
|
@@ -143,8 +137,8 @@ def extract_names_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
143
137
|
with tarfile.open(fileobj=file_stream, mode=mode) as tf:
|
|
144
138
|
for member in tf.getmembers():
|
|
145
139
|
name = Path(member.name)
|
|
146
|
-
new_path_ext = get_path_suffix(name)
|
|
147
140
|
is_dir = member.isdir()
|
|
141
|
+
new_path_ext = get_path_suffix(name, is_dir)
|
|
148
142
|
|
|
149
143
|
if not archive_should_skip(
|
|
150
144
|
name,
|
|
@@ -203,10 +197,10 @@ def extract_text_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
203
197
|
for info in f.infolist():
|
|
204
198
|
if info.is_dir():
|
|
205
199
|
continue
|
|
206
|
-
|
|
200
|
+
|
|
207
201
|
file_name = Path(info.filename)
|
|
208
202
|
new_path_ext = get_path_suffix(file_name)
|
|
209
|
-
|
|
203
|
+
|
|
210
204
|
if archive_should_skip(
|
|
211
205
|
file_name,
|
|
212
206
|
config,
|
|
@@ -243,7 +237,7 @@ def extract_text_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
243
237
|
|
|
244
238
|
file_name = Path(info.filename)
|
|
245
239
|
new_path_ext = get_path_suffix(file_name)
|
|
246
|
-
|
|
240
|
+
|
|
247
241
|
if archive_should_skip(
|
|
248
242
|
file_name,
|
|
249
243
|
config,
|
|
@@ -282,7 +276,7 @@ def extract_text_from_archive(file_path: Path, config, depth: int = -1,
|
|
|
282
276
|
|
|
283
277
|
file_name = Path(member.name)
|
|
284
278
|
new_path_ext = get_path_suffix(file_name)
|
|
285
|
-
|
|
279
|
+
|
|
286
280
|
if archive_should_skip(
|
|
287
281
|
file_name,
|
|
288
282
|
config,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
|
+
from itertools import permutations
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
|
|
4
6
|
import click
|
|
5
7
|
from lark import Lark, Transformer
|
|
@@ -16,7 +18,16 @@ class ExprNode:
|
|
|
16
18
|
|
|
17
19
|
class TermNode(ExprNode):
|
|
18
20
|
"""Node representing a single search term"""
|
|
19
|
-
def __init__(
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
term: str,
|
|
24
|
+
regex: bool,
|
|
25
|
+
whole_word: bool,
|
|
26
|
+
case_sensitive: bool,
|
|
27
|
+
fuzzy: bool,
|
|
28
|
+
fuzzy_level: int | None,
|
|
29
|
+
full_path: bool
|
|
30
|
+
):
|
|
20
31
|
self.raw_term = term
|
|
21
32
|
self.term_lower = term.lower()
|
|
22
33
|
self.regex = regex
|
|
@@ -24,6 +35,7 @@ class TermNode(ExprNode):
|
|
|
24
35
|
self.case_sensitive = case_sensitive
|
|
25
36
|
self.fuzzy = fuzzy
|
|
26
37
|
self.fuzzy_level = fuzzy_level
|
|
38
|
+
self.full_path = full_path
|
|
27
39
|
|
|
28
40
|
if not fuzzy:
|
|
29
41
|
flags = 0 if case_sensitive else re.IGNORECASE # Adjust case sensitivity
|
|
@@ -37,7 +49,10 @@ class TermNode(ExprNode):
|
|
|
37
49
|
|
|
38
50
|
self.pattern = compile_regex(term, flags) # Precompile the regex pattern for performance
|
|
39
51
|
|
|
40
|
-
def evaluate(self, text: str) -> bool:
|
|
52
|
+
def evaluate(self, text: str | Path) -> bool:
|
|
53
|
+
if isinstance(text, Path):
|
|
54
|
+
text = text.name if not self.full_path else str(text)
|
|
55
|
+
|
|
41
56
|
# Fuzzy search mode
|
|
42
57
|
text_cmp = text if self.case_sensitive else text.lower()
|
|
43
58
|
term = self.raw_term if self.case_sensitive else self.term_lower
|
|
@@ -114,8 +129,25 @@ class OrNode(ExprNode):
|
|
|
114
129
|
return self.left.evaluate(text) or self.right.evaluate(text)
|
|
115
130
|
|
|
116
131
|
|
|
132
|
+
def gvp() -> str:
|
|
133
|
+
"""Generate all possible states and permutations of input prefixes
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: prefixes=("f", "r", "rf") -> result="f|r|rf|fr"
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
prefixes = {'r','c','w','f','p','rp','pc','wp','fp','rc','cw',
|
|
140
|
+
'cf','wf','cwf','rcp','cwp','cfp','wfp','cwfp'}
|
|
141
|
+
valid_prefixes = set()
|
|
142
|
+
|
|
143
|
+
for p in prefixes:
|
|
144
|
+
valid_prefixes |= {''.join(pr) for pr in permutations(p)}
|
|
145
|
+
|
|
146
|
+
return '|'.join(valid_prefixes)
|
|
147
|
+
|
|
148
|
+
|
|
117
149
|
# Lark grammar for parsing logical expressions
|
|
118
|
-
query_grammar =
|
|
150
|
+
query_grammar = rf"""
|
|
119
151
|
?start: expr
|
|
120
152
|
|
|
121
153
|
?expr: or_expr
|
|
@@ -133,7 +165,7 @@ query_grammar = r"""
|
|
|
133
165
|
| ESCAPED_STRING -> string
|
|
134
166
|
| "(" expr ")"
|
|
135
167
|
|
|
136
|
-
PREFIXED_STRING: /(
|
|
168
|
+
PREFIXED_STRING: /({gvp()})"([^"\\]|\\.)*"/
|
|
137
169
|
|
|
138
170
|
%import common.ESCAPED_STRING
|
|
139
171
|
%import common.WS
|
|
@@ -171,7 +203,8 @@ class TreeToExpr(Transformer):
|
|
|
171
203
|
False,
|
|
172
204
|
False,
|
|
173
205
|
False,
|
|
174
|
-
None
|
|
206
|
+
None,
|
|
207
|
+
False
|
|
175
208
|
)
|
|
176
209
|
|
|
177
210
|
def prefixed_string(self, s):
|
|
@@ -179,7 +212,7 @@ class TreeToExpr(Transformer):
|
|
|
179
212
|
text = str(s[0]) # e.g., 'rc"pattern"'
|
|
180
213
|
|
|
181
214
|
quote_index = text.index('"')
|
|
182
|
-
prefix = text[:quote_index]
|
|
215
|
+
prefix = text[:quote_index]
|
|
183
216
|
quoted = text[quote_index:]
|
|
184
217
|
|
|
185
218
|
is_regex = 'r' in prefix
|
|
@@ -191,7 +224,8 @@ class TreeToExpr(Transformer):
|
|
|
191
224
|
whole_word='w' in prefix,
|
|
192
225
|
case_sensitive='c' in prefix,
|
|
193
226
|
fuzzy='f' in prefix,
|
|
194
|
-
fuzzy_level=self.fuzzy_level
|
|
227
|
+
fuzzy_level=self.fuzzy_level,
|
|
228
|
+
full_path='p' in prefix
|
|
195
229
|
)
|
|
196
230
|
|
|
197
231
|
def and_expr(self, args):
|
|
@@ -220,7 +254,8 @@ def parse_query_expression(config) -> ExprNode:
|
|
|
220
254
|
config.word,
|
|
221
255
|
config.case_sensitive,
|
|
222
256
|
config.fuzzy,
|
|
223
|
-
config.fuzzy_level
|
|
257
|
+
config.fuzzy_level,
|
|
258
|
+
config.full_path
|
|
224
259
|
)
|
|
225
260
|
|
|
226
261
|
# Otherwise, parse using Lark
|
|
@@ -232,8 +267,12 @@ def parse_query_expression(config) -> ExprNode:
|
|
|
232
267
|
raise click.exceptions.Exit(1)
|
|
233
268
|
|
|
234
269
|
|
|
235
|
-
def find_term_matches(node: TermNode, text: str, num: int) -> list[tuple[int, int]]:
|
|
270
|
+
def find_term_matches(node: TermNode, text: str | Path, num: int) -> list[tuple[int, int]]:
|
|
236
271
|
"""Find matching spans for a single term"""
|
|
272
|
+
if isinstance(text, Path):
|
|
273
|
+
text = text.name if not node.full_path else str(text)
|
|
274
|
+
# Calculate number of chars that come before name of file or dir
|
|
275
|
+
num -= len(text)
|
|
237
276
|
|
|
238
277
|
if node.fuzzy:
|
|
239
278
|
# Fuzzy partial matching currently has no span extraction
|
|
@@ -270,7 +309,7 @@ def find_term_matches(node: TermNode, text: str, num: int) -> list[tuple[int, in
|
|
|
270
309
|
|
|
271
310
|
def evaluate_with_matches(
|
|
272
311
|
node: ExprNode,
|
|
273
|
-
text: str,
|
|
312
|
+
text: str | Path,
|
|
274
313
|
num: int,
|
|
275
314
|
positive: bool = True
|
|
276
315
|
) -> tuple[bool, list[tuple[int, int]]]:
|
|
@@ -370,12 +409,10 @@ def evaluate_with_matches(
|
|
|
370
409
|
|
|
371
410
|
return True, matches
|
|
372
411
|
|
|
373
|
-
raise TypeError(
|
|
374
|
-
f'Unsupported expression node: {type(node).__name__}'
|
|
375
|
-
)
|
|
412
|
+
raise TypeError(f'Unsupported expression node: {type(node).__name__}')
|
|
376
413
|
|
|
377
414
|
|
|
378
|
-
def find_matches(expr: ExprNode, text: str, num: int = 0) -> list[tuple[int, int]]:
|
|
415
|
+
def find_matches(expr: ExprNode, text: str | Path, num: int = 0) -> list[tuple[int, int]]:
|
|
379
416
|
"""Find spans that contribute to a successful expression"""
|
|
380
417
|
|
|
381
418
|
if expr is None:
|
|
@@ -68,15 +68,13 @@ def search_file_and_dir(config, matches: dict, pattern, p: Path,
|
|
|
68
68
|
'directory' if config.directory and p.is_dir() else
|
|
69
69
|
None
|
|
70
70
|
)
|
|
71
|
-
p_name = p.name if not config.full_path else path_str
|
|
72
71
|
|
|
73
|
-
if match_type and (pattern is None or pattern.evaluate(
|
|
72
|
+
if match_type and (pattern is None or pattern.evaluate(p)):
|
|
74
73
|
# Find matched query in the name
|
|
75
74
|
name_matches = find_matches(
|
|
76
75
|
pattern,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
len(path_str) - len(p_name)
|
|
76
|
+
p,
|
|
77
|
+
len(path_str)
|
|
80
78
|
)
|
|
81
79
|
|
|
82
80
|
add_result(
|
|
@@ -131,14 +129,12 @@ def search_file_and_dir(config, matches: dict, pattern, p: Path,
|
|
|
131
129
|
'directory' if config.directory and is_dir else
|
|
132
130
|
None
|
|
133
131
|
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if arc_match_type and (pattern is None or pattern.evaluate(virtual_name)):
|
|
132
|
+
|
|
133
|
+
if arc_match_type and (pattern is None or pattern.evaluate(name)):
|
|
137
134
|
name_matches = find_matches(
|
|
138
135
|
pattern,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
len(name_str) - len(virtual_name)
|
|
136
|
+
name,
|
|
137
|
+
len(name_str)
|
|
142
138
|
)
|
|
143
139
|
|
|
144
140
|
add_result(
|
|
@@ -310,41 +306,50 @@ def search_content(config, matches: dict, pattern, binary_pattern,
|
|
|
310
306
|
return
|
|
311
307
|
|
|
312
308
|
collector = ContextCollector(config.context)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
309
|
+
try:
|
|
310
|
+
# Memory-map the file for efficient access
|
|
311
|
+
with open(p, 'rb') as f, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
|
|
312
|
+
head = mm[:8192]
|
|
313
|
+
if is_binary(head) or (binary_pattern and not content_contains(mm, binary_pattern)):
|
|
314
|
+
return
|
|
318
315
|
|
|
319
|
-
|
|
316
|
+
mm.seek(0) # Move the cursor to the beginning of the file
|
|
320
317
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
except UnicodeDecodeError:
|
|
326
|
-
# Skip lines that can't be decoded
|
|
327
|
-
continue
|
|
318
|
+
# Iterate over each line in the file
|
|
319
|
+
for num, line in enumerate(iter(mm.readline, b''), 1):
|
|
320
|
+
if binary_pattern and not content_contains(line, binary_pattern):
|
|
321
|
+
continue
|
|
328
322
|
|
|
329
|
-
|
|
323
|
+
try:
|
|
324
|
+
line_decoded = line.decode('utf-8').rstrip('\r\n')
|
|
325
|
+
except UnicodeDecodeError:
|
|
326
|
+
# Skip lines that can't be decoded
|
|
327
|
+
continue
|
|
330
328
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
329
|
+
matched = pattern is None or pattern.evaluate(line_decoded)
|
|
330
|
+
|
|
331
|
+
# Avoid searching through the entire file content if the paths-only flag is True
|
|
332
|
+
if config.paths_only and matched:
|
|
333
|
+
add_result(
|
|
334
|
+
matches,
|
|
335
|
+
result_queue,
|
|
336
|
+
'content',
|
|
337
|
+
ContentResult(path=path_str),
|
|
338
|
+
result_lock
|
|
339
|
+
)
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
if not any(config.context) and not matched:
|
|
343
|
+
continue
|
|
341
344
|
|
|
342
|
-
|
|
345
|
+
line_instance = Line(num, line_decoded)
|
|
343
346
|
|
|
344
|
-
|
|
345
|
-
|
|
347
|
+
if matched:
|
|
348
|
+
line_instance.matches = find_matches(pattern, line_decoded)
|
|
346
349
|
|
|
347
|
-
|
|
350
|
+
collector.process(line_instance, matched)
|
|
351
|
+
except OSError:
|
|
352
|
+
return
|
|
348
353
|
|
|
349
354
|
groups = collector.finish()
|
|
350
355
|
if groups:
|
|
@@ -452,7 +457,7 @@ def walk_logic(path: Path, config, depth: int, matcher, ancestors: set):
|
|
|
452
457
|
ancestors.remove(dir_key)
|
|
453
458
|
|
|
454
459
|
|
|
455
|
-
# TODO: Make traversal
|
|
460
|
+
# TODO: Make traversal parallel just like seek func
|
|
456
461
|
def walk(config):
|
|
457
462
|
"""Include paths define traversal roots, avoiding unnecessary traversal
|
|
458
463
|
of unrelated parts of the tree"""
|
|
@@ -24,13 +24,19 @@ def compile_regex(txt, flags=0) -> re.Pattern | None:
|
|
|
24
24
|
raise click.exceptions.Exit(1)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def get_path_suffix(path: Path | str) -> str:
|
|
27
|
+
def get_path_suffix(path: Path | str, is_dir: bool | None = None) -> str:
|
|
28
28
|
""" If multiple file suffixes are valid, return them, otherwise return only the last suffix """
|
|
29
29
|
if isinstance(path, str):
|
|
30
30
|
path = Path(path)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# The paths within archive aren't physical paths,
|
|
33
|
+
# so we need to determine is_dir from their metadata.
|
|
34
|
+
if is_dir is not None:
|
|
35
|
+
if is_dir:
|
|
36
|
+
return None
|
|
37
|
+
else:
|
|
38
|
+
if path.is_dir():
|
|
39
|
+
return None
|
|
34
40
|
|
|
35
41
|
suffixes = ''.join(path.suffixes)[1:].lower()
|
|
36
42
|
|
|
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
|
|
File without changes
|