pseek 3.6.2__tar.gz → 3.7.1__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.2 → pseek-3.7.1}/PKG-INFO +63 -27
- {pseek-3.6.2 → pseek-3.7.1}/README.md +62 -26
- {pseek-3.6.2 → pseek-3.7.1}/pseek/cli.py +6 -4
- {pseek-3.6.2 → pseek-3.7.1}/pseek/parser.py +45 -32
- {pseek-3.6.2 → pseek-3.7.1}/pseek/searcher.py +65 -25
- {pseek-3.6.2 → pseek-3.7.1}/pseek/structs.py +1 -0
- {pseek-3.6.2 → pseek-3.7.1}/pseek/utils.py +1 -1
- {pseek-3.6.2 → pseek-3.7.1}/pyproject.toml +1 -1
- {pseek-3.6.2 → pseek-3.7.1}/.gitattributes +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/.github/workflows/publish.yml +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/.gitignore +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/Cargo.lock +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/Cargo.toml +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/LICENSE +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/pseek/__init__.py +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/pseek/archive.py +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/pseek/compound_extensions +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/scripts/check_version.py +0 -0
- {pseek-3.6.2 → pseek-3.7.1}/src/lib.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pseek
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.1
|
|
4
4
|
Classifier: Development Status :: 5 - Production/Stable
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -51,6 +51,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
51
51
|
* [Fuzzy](#fuzzy-search)
|
|
52
52
|
* [Fuzzy Similarity Threshold](#fuzzy-similarity-threshold)
|
|
53
53
|
* [Full Path](#full-path)
|
|
54
|
+
* [Line](#line)
|
|
54
55
|
* [Expression Queries](#expression-queries)
|
|
55
56
|
* [Operators](#supported-operators)
|
|
56
57
|
* [AND](#and)
|
|
@@ -63,6 +64,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
63
64
|
* [Whole Word](#whole-word)
|
|
64
65
|
* [Fuzzy](#fuzzy)
|
|
65
66
|
* [Full Path](#full-path-1)
|
|
67
|
+
* [Line](#line-1)
|
|
66
68
|
* [Combined Prefixes](#combined-prefixes)
|
|
67
69
|
* [Automatic Filtering](#automatic-filtering)
|
|
68
70
|
* [Manual Filtering](#manual-filtering)
|
|
@@ -215,6 +217,7 @@ psk --exclude build
|
|
|
215
217
|
| `--expr` | Enable boolean query expressions. Example: `r"foo.*bar" and ("bar" or "baz") and not "qux"`. Prefixes: `r=regex`, `c=case-sensitive`, `w=whole-word`, `f=fuzzy` |
|
|
216
218
|
| `--context` | Show N context lines before and after matches, or use N:M to specify before:after |
|
|
217
219
|
| `--full-path` | By default, query is only matched against the filename or directory name (this applies only when searching for files or directories, not file contents). Using this flag, query is matched against the full path (and, if `--absolute-path` flag is enabled, against the absolute path) |
|
|
220
|
+
| `--line` | Match lines where the entire line participates in a match |
|
|
218
221
|
| `--depth` | Limit directory traversal to given depth range. By default, there is no limit on search depth |
|
|
219
222
|
| `--timeout` | Stop the search after the specified number of seconds |
|
|
220
223
|
| `--fuzzy` | Enable fuzzy search (Highlighting and counting matches are disabled in this mode if `--word` is not enabled to prevent the program from slowing down). except when `--expr` is enabled, in which case you can make it fuzzy by putting `f` before term: `f"foo"` |
|
|
@@ -270,6 +273,8 @@ psk backup --directory
|
|
|
270
273
|
psk TODO --content
|
|
271
274
|
```
|
|
272
275
|
|
|
276
|
+
> **Note:** When searching through file contents, binary files are automatically ignored, and this behavior cannot be changed. If null bytes are present within the first 8 KiB of a file, it is identified as a binary file (The entire file is not examined, in order to increase speed).
|
|
277
|
+
|
|
273
278
|
### Search Everywhere
|
|
274
279
|
|
|
275
280
|
```bash
|
|
@@ -399,6 +404,39 @@ With `--full-path`, you can match `fd/f` against the entry `fd/file.txt` inside
|
|
|
399
404
|
|
|
400
405
|
> **Note:** For ordinary queries without `--regex`, backslashes are treated literally, so Windows paths can be searched without additional escaping. When using `--expr`, quoted terms use JSON-style escaping; escape backslashes accordingly. For example, `"\\"` represents a single literal backslash, while `"\\n"` represents the literal sequence `\n`.
|
|
401
406
|
|
|
407
|
+
### Line
|
|
408
|
+
|
|
409
|
+
Match the entire line or selected path instead of matching a pattern anywhere within it.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Match lines exactly equal to "TODO"
|
|
413
|
+
psk TODO --line
|
|
414
|
+
|
|
415
|
+
# Match only lines containing exactly "foo" or "bar"
|
|
416
|
+
psk 'foo|bar' --regex --line
|
|
417
|
+
|
|
418
|
+
# Match files whose names are exactly "cli.py"
|
|
419
|
+
psk "cli.py" --file --line
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
> **Note:** If both options are specified, `--line` takes precedence over `--word`. In this case, instead of checking individual word boundaries, the entire line or selected path is matched.
|
|
423
|
+
|
|
424
|
+
By default, Pseek matches a file's or directory's **name**. Use `--full-path` to match against its full path instead.
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
# Match files named "cli.py"
|
|
428
|
+
psk "cli.py" --file --line
|
|
429
|
+
|
|
430
|
+
# Match the full relative path "pseek/cli.py"
|
|
431
|
+
psk "pseek/cli.py" --file --full-path --line
|
|
432
|
+
|
|
433
|
+
# Match the full absolute path
|
|
434
|
+
psk "/home/python/cli/pseek/utils.py" \
|
|
435
|
+
--file --full-path --absolute-path --line
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
> **Note:** Leading and trailing whitespace is significant. `--line` does not trim the target before matching. Use an appropriate regular expression, such as `\s*TODO\s*` with `--line`, if surrounding whitespace should be ignored.
|
|
439
|
+
|
|
402
440
|
## Expression Queries
|
|
403
441
|
|
|
404
442
|
Expression mode enables logical search expressions.
|
|
@@ -453,9 +491,7 @@ Each term can have its own search mode.
|
|
|
453
491
|
|
|
454
492
|
#### Regex
|
|
455
493
|
|
|
456
|
-
|
|
457
|
-
r"pattern"
|
|
458
|
-
```
|
|
494
|
+
Usage: `r"pattern"`
|
|
459
495
|
|
|
460
496
|
Example:
|
|
461
497
|
|
|
@@ -465,9 +501,7 @@ psk 'r"error\d+"' --expr
|
|
|
465
501
|
|
|
466
502
|
#### Case Sensitive
|
|
467
503
|
|
|
468
|
-
|
|
469
|
-
c"text"
|
|
470
|
-
```
|
|
504
|
+
Usage: `c"text"`
|
|
471
505
|
|
|
472
506
|
Example:
|
|
473
507
|
|
|
@@ -477,9 +511,7 @@ psk 'c"Error"' --expr
|
|
|
477
511
|
|
|
478
512
|
#### Whole Word
|
|
479
513
|
|
|
480
|
-
|
|
481
|
-
w"text"
|
|
482
|
-
```
|
|
514
|
+
Usage: `w"text"`
|
|
483
515
|
|
|
484
516
|
Example:
|
|
485
517
|
|
|
@@ -489,9 +521,7 @@ psk 'w"cat"' --expr
|
|
|
489
521
|
|
|
490
522
|
#### Fuzzy
|
|
491
523
|
|
|
492
|
-
|
|
493
|
-
f"text"
|
|
494
|
-
```
|
|
524
|
+
Usage: `f"text"`
|
|
495
525
|
|
|
496
526
|
Example:
|
|
497
527
|
|
|
@@ -501,9 +531,7 @@ psk 'f"apple"' --expr
|
|
|
501
531
|
|
|
502
532
|
#### Full Path
|
|
503
533
|
|
|
504
|
-
|
|
505
|
-
p"text"
|
|
506
|
-
```
|
|
534
|
+
Usage: `p"text"`
|
|
507
535
|
|
|
508
536
|
Example:
|
|
509
537
|
|
|
@@ -511,6 +539,16 @@ Example:
|
|
|
511
539
|
psk 'p"lib"' --expr
|
|
512
540
|
```
|
|
513
541
|
|
|
542
|
+
#### Line
|
|
543
|
+
|
|
544
|
+
Usage: `l"text"`
|
|
545
|
+
|
|
546
|
+
Example:
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
psk 'l"foo"' --expr
|
|
550
|
+
```
|
|
551
|
+
|
|
514
552
|
#### Combined Prefixes
|
|
515
553
|
|
|
516
554
|
Prefixes can be combined.
|
|
@@ -538,9 +576,7 @@ Meaning:
|
|
|
538
576
|
|
|
539
577
|
simultaneously.
|
|
540
578
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
and their permutations.
|
|
579
|
+
All modes are permitted, except for those where regex and word, or regex and fuzzy, are used simultaneously.
|
|
544
580
|
|
|
545
581
|
> **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"`
|
|
546
582
|
|
|
@@ -723,12 +759,12 @@ Controls how deep the search goes inside directories.
|
|
|
723
759
|
A depth of `0` means that only base path's immediate contents are searched; subdirectories are not entered.
|
|
724
760
|
|
|
725
761
|
```text
|
|
726
|
-
project/
|
|
727
|
-
├── file.txt
|
|
728
|
-
├── folder1/
|
|
729
|
-
│ ├── file.txt
|
|
730
|
-
│ └── folder2/
|
|
731
|
-
│ └── file.txt
|
|
762
|
+
project/ <- base directory
|
|
763
|
+
├── file.txt <- depth 0
|
|
764
|
+
├── folder1/ <- depth 0
|
|
765
|
+
│ ├── file.txt <- depth 1
|
|
766
|
+
│ └── folder2/ <- depth 1
|
|
767
|
+
│ └── file.txt <- depth 2
|
|
732
768
|
```
|
|
733
769
|
|
|
734
770
|
`--depth` can be specified multiple times to search non-contiguous depth ranges:
|
|
@@ -931,7 +967,7 @@ If the search exceeds the limit, it will be terminated. Results found before the
|
|
|
931
967
|
Display a summary of the search, including result counts, scanned paths, archive information, and search time.
|
|
932
968
|
|
|
933
969
|
```bash
|
|
934
|
-
pseek config --stats
|
|
970
|
+
pseek config --archive --stats
|
|
935
971
|
```
|
|
936
972
|
|
|
937
973
|
Example output:
|
|
@@ -940,7 +976,7 @@ Example output:
|
|
|
940
976
|
Statistics
|
|
941
977
|
────────────────
|
|
942
978
|
Results
|
|
943
|
-
Files: 12
|
|
979
|
+
Files matched: 12
|
|
944
980
|
Directories matched: 3
|
|
945
981
|
Files with matched content: 8
|
|
946
982
|
Lines matched: 24
|
|
@@ -23,6 +23,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
23
23
|
* [Fuzzy](#fuzzy-search)
|
|
24
24
|
* [Fuzzy Similarity Threshold](#fuzzy-similarity-threshold)
|
|
25
25
|
* [Full Path](#full-path)
|
|
26
|
+
* [Line](#line)
|
|
26
27
|
* [Expression Queries](#expression-queries)
|
|
27
28
|
* [Operators](#supported-operators)
|
|
28
29
|
* [AND](#and)
|
|
@@ -35,6 +36,7 @@ Fast and powerful command-line search tool for finding files, directories, and t
|
|
|
35
36
|
* [Whole Word](#whole-word)
|
|
36
37
|
* [Fuzzy](#fuzzy)
|
|
37
38
|
* [Full Path](#full-path-1)
|
|
39
|
+
* [Line](#line-1)
|
|
38
40
|
* [Combined Prefixes](#combined-prefixes)
|
|
39
41
|
* [Automatic Filtering](#automatic-filtering)
|
|
40
42
|
* [Manual Filtering](#manual-filtering)
|
|
@@ -187,6 +189,7 @@ psk --exclude build
|
|
|
187
189
|
| `--expr` | Enable boolean query expressions. Example: `r"foo.*bar" and ("bar" or "baz") and not "qux"`. Prefixes: `r=regex`, `c=case-sensitive`, `w=whole-word`, `f=fuzzy` |
|
|
188
190
|
| `--context` | Show N context lines before and after matches, or use N:M to specify before:after |
|
|
189
191
|
| `--full-path` | By default, query is only matched against the filename or directory name (this applies only when searching for files or directories, not file contents). Using this flag, query is matched against the full path (and, if `--absolute-path` flag is enabled, against the absolute path) |
|
|
192
|
+
| `--line` | Match lines where the entire line participates in a match |
|
|
190
193
|
| `--depth` | Limit directory traversal to given depth range. By default, there is no limit on search depth |
|
|
191
194
|
| `--timeout` | Stop the search after the specified number of seconds |
|
|
192
195
|
| `--fuzzy` | Enable fuzzy search (Highlighting and counting matches are disabled in this mode if `--word` is not enabled to prevent the program from slowing down). except when `--expr` is enabled, in which case you can make it fuzzy by putting `f` before term: `f"foo"` |
|
|
@@ -242,6 +245,8 @@ psk backup --directory
|
|
|
242
245
|
psk TODO --content
|
|
243
246
|
```
|
|
244
247
|
|
|
248
|
+
> **Note:** When searching through file contents, binary files are automatically ignored, and this behavior cannot be changed. If null bytes are present within the first 8 KiB of a file, it is identified as a binary file (The entire file is not examined, in order to increase speed).
|
|
249
|
+
|
|
245
250
|
### Search Everywhere
|
|
246
251
|
|
|
247
252
|
```bash
|
|
@@ -371,6 +376,39 @@ With `--full-path`, you can match `fd/f` against the entry `fd/file.txt` inside
|
|
|
371
376
|
|
|
372
377
|
> **Note:** For ordinary queries without `--regex`, backslashes are treated literally, so Windows paths can be searched without additional escaping. When using `--expr`, quoted terms use JSON-style escaping; escape backslashes accordingly. For example, `"\\"` represents a single literal backslash, while `"\\n"` represents the literal sequence `\n`.
|
|
373
378
|
|
|
379
|
+
### Line
|
|
380
|
+
|
|
381
|
+
Match the entire line or selected path instead of matching a pattern anywhere within it.
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Match lines exactly equal to "TODO"
|
|
385
|
+
psk TODO --line
|
|
386
|
+
|
|
387
|
+
# Match only lines containing exactly "foo" or "bar"
|
|
388
|
+
psk 'foo|bar' --regex --line
|
|
389
|
+
|
|
390
|
+
# Match files whose names are exactly "cli.py"
|
|
391
|
+
psk "cli.py" --file --line
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
> **Note:** If both options are specified, `--line` takes precedence over `--word`. In this case, instead of checking individual word boundaries, the entire line or selected path is matched.
|
|
395
|
+
|
|
396
|
+
By default, Pseek matches a file's or directory's **name**. Use `--full-path` to match against its full path instead.
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# Match files named "cli.py"
|
|
400
|
+
psk "cli.py" --file --line
|
|
401
|
+
|
|
402
|
+
# Match the full relative path "pseek/cli.py"
|
|
403
|
+
psk "pseek/cli.py" --file --full-path --line
|
|
404
|
+
|
|
405
|
+
# Match the full absolute path
|
|
406
|
+
psk "/home/python/cli/pseek/utils.py" \
|
|
407
|
+
--file --full-path --absolute-path --line
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
> **Note:** Leading and trailing whitespace is significant. `--line` does not trim the target before matching. Use an appropriate regular expression, such as `\s*TODO\s*` with `--line`, if surrounding whitespace should be ignored.
|
|
411
|
+
|
|
374
412
|
## Expression Queries
|
|
375
413
|
|
|
376
414
|
Expression mode enables logical search expressions.
|
|
@@ -425,9 +463,7 @@ Each term can have its own search mode.
|
|
|
425
463
|
|
|
426
464
|
#### Regex
|
|
427
465
|
|
|
428
|
-
|
|
429
|
-
r"pattern"
|
|
430
|
-
```
|
|
466
|
+
Usage: `r"pattern"`
|
|
431
467
|
|
|
432
468
|
Example:
|
|
433
469
|
|
|
@@ -437,9 +473,7 @@ psk 'r"error\d+"' --expr
|
|
|
437
473
|
|
|
438
474
|
#### Case Sensitive
|
|
439
475
|
|
|
440
|
-
|
|
441
|
-
c"text"
|
|
442
|
-
```
|
|
476
|
+
Usage: `c"text"`
|
|
443
477
|
|
|
444
478
|
Example:
|
|
445
479
|
|
|
@@ -449,9 +483,7 @@ psk 'c"Error"' --expr
|
|
|
449
483
|
|
|
450
484
|
#### Whole Word
|
|
451
485
|
|
|
452
|
-
|
|
453
|
-
w"text"
|
|
454
|
-
```
|
|
486
|
+
Usage: `w"text"`
|
|
455
487
|
|
|
456
488
|
Example:
|
|
457
489
|
|
|
@@ -461,9 +493,7 @@ psk 'w"cat"' --expr
|
|
|
461
493
|
|
|
462
494
|
#### Fuzzy
|
|
463
495
|
|
|
464
|
-
|
|
465
|
-
f"text"
|
|
466
|
-
```
|
|
496
|
+
Usage: `f"text"`
|
|
467
497
|
|
|
468
498
|
Example:
|
|
469
499
|
|
|
@@ -473,9 +503,7 @@ psk 'f"apple"' --expr
|
|
|
473
503
|
|
|
474
504
|
#### Full Path
|
|
475
505
|
|
|
476
|
-
|
|
477
|
-
p"text"
|
|
478
|
-
```
|
|
506
|
+
Usage: `p"text"`
|
|
479
507
|
|
|
480
508
|
Example:
|
|
481
509
|
|
|
@@ -483,6 +511,16 @@ Example:
|
|
|
483
511
|
psk 'p"lib"' --expr
|
|
484
512
|
```
|
|
485
513
|
|
|
514
|
+
#### Line
|
|
515
|
+
|
|
516
|
+
Usage: `l"text"`
|
|
517
|
+
|
|
518
|
+
Example:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
psk 'l"foo"' --expr
|
|
522
|
+
```
|
|
523
|
+
|
|
486
524
|
#### Combined Prefixes
|
|
487
525
|
|
|
488
526
|
Prefixes can be combined.
|
|
@@ -510,9 +548,7 @@ Meaning:
|
|
|
510
548
|
|
|
511
549
|
simultaneously.
|
|
512
550
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
and their permutations.
|
|
551
|
+
All modes are permitted, except for those where regex and word, or regex and fuzzy, are used simultaneously.
|
|
516
552
|
|
|
517
553
|
> **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"`
|
|
518
554
|
|
|
@@ -695,12 +731,12 @@ Controls how deep the search goes inside directories.
|
|
|
695
731
|
A depth of `0` means that only base path's immediate contents are searched; subdirectories are not entered.
|
|
696
732
|
|
|
697
733
|
```text
|
|
698
|
-
project/
|
|
699
|
-
├── file.txt
|
|
700
|
-
├── folder1/
|
|
701
|
-
│ ├── file.txt
|
|
702
|
-
│ └── folder2/
|
|
703
|
-
│ └── file.txt
|
|
734
|
+
project/ <- base directory
|
|
735
|
+
├── file.txt <- depth 0
|
|
736
|
+
├── folder1/ <- depth 0
|
|
737
|
+
│ ├── file.txt <- depth 1
|
|
738
|
+
│ └── folder2/ <- depth 1
|
|
739
|
+
│ └── file.txt <- depth 2
|
|
704
740
|
```
|
|
705
741
|
|
|
706
742
|
`--depth` can be specified multiple times to search non-contiguous depth ranges:
|
|
@@ -903,7 +939,7 @@ If the search exceeds the limit, it will be terminated. Results found before the
|
|
|
903
939
|
Display a summary of the search, including result counts, scanned paths, archive information, and search time.
|
|
904
940
|
|
|
905
941
|
```bash
|
|
906
|
-
pseek config --stats
|
|
942
|
+
pseek config --archive --stats
|
|
907
943
|
```
|
|
908
944
|
|
|
909
945
|
Example output:
|
|
@@ -912,7 +948,7 @@ Example output:
|
|
|
912
948
|
Statistics
|
|
913
949
|
────────────────
|
|
914
950
|
Results
|
|
915
|
-
Files: 12
|
|
951
|
+
Files matched: 12
|
|
916
952
|
Directories matched: 3
|
|
917
953
|
Files with matched content: 8
|
|
918
954
|
Lines matched: 24
|
|
@@ -49,7 +49,7 @@ def build_highlight(text, matches):
|
|
|
49
49
|
return ''.join(parts)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def echo(results: dict, context:
|
|
52
|
+
def echo(results: dict, context: tuple[int, int]):
|
|
53
53
|
"""Display results with a specific format and color scheme"""
|
|
54
54
|
for match_type, datas in results.items():
|
|
55
55
|
if datas:
|
|
@@ -258,7 +258,7 @@ def search_with_timeout(config):
|
|
|
258
258
|
time.sleep(0.0005)
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
def check_rar_backend(archive_enabled: bool, tool_path: str, backend: str):
|
|
261
|
+
def check_rar_backend(archive_enabled: bool, tool_path: str | None, backend: str):
|
|
262
262
|
"""Check for the existence of rar backend or save and set it for rarfile"""
|
|
263
263
|
|
|
264
264
|
# Save backend path for later executions
|
|
@@ -347,6 +347,8 @@ def check_rar_backend(archive_enabled: bool, tool_path: str, backend: str):
|
|
|
347
347
|
'(this applies only when searching for files or directories, not file contents). '
|
|
348
348
|
'Using this flag, query is matched against the full path '
|
|
349
349
|
'(and, if --absolute-path flag is enabled, against the absolute path).')
|
|
350
|
+
@click.option('--line', is_flag=True,
|
|
351
|
+
help='Match lines where the entire line participates in a match.')
|
|
350
352
|
@click.option('-D', '--depth', multiple=True, metavar='MIN:MAX',
|
|
351
353
|
help='Limit directory traversal to given depth range. '
|
|
352
354
|
'By default, there is no limit on search depth.')
|
|
@@ -440,10 +442,10 @@ def search(**kwargs):
|
|
|
440
442
|
|
|
441
443
|
check_rar_backend(config.archive, config.rar_backend, config.query)
|
|
442
444
|
|
|
443
|
-
if not config.expr and config.fuzzy:
|
|
445
|
+
if not config.expr and config.fuzzy and not config.line:
|
|
444
446
|
if not config.word:
|
|
445
447
|
click.secho(
|
|
446
|
-
"Warning:
|
|
448
|
+
"Warning: To improve performance, highlighting of matches is disabled.",
|
|
447
449
|
fg="yellow"
|
|
448
450
|
)
|
|
449
451
|
elif config.word and " " in config.query:
|
|
@@ -26,7 +26,8 @@ class TermNode(ExprNode):
|
|
|
26
26
|
case_sensitive: bool,
|
|
27
27
|
fuzzy: bool,
|
|
28
28
|
fuzzy_level: int | None,
|
|
29
|
-
full_path: bool
|
|
29
|
+
full_path: bool,
|
|
30
|
+
whole_line: bool
|
|
30
31
|
):
|
|
31
32
|
self.raw_term = term
|
|
32
33
|
self.term_lower = term.lower()
|
|
@@ -36,6 +37,7 @@ class TermNode(ExprNode):
|
|
|
36
37
|
self.fuzzy = fuzzy
|
|
37
38
|
self.fuzzy_level = fuzzy_level
|
|
38
39
|
self.full_path = full_path
|
|
40
|
+
self.whole_line = whole_line
|
|
39
41
|
|
|
40
42
|
if not fuzzy:
|
|
41
43
|
flags = 0 if case_sensitive else re.IGNORECASE # Adjust case sensitivity
|
|
@@ -44,7 +46,7 @@ class TermNode(ExprNode):
|
|
|
44
46
|
if not regex:
|
|
45
47
|
term = re.escape(term) # Escape if not regex
|
|
46
48
|
# Apply whole-word matching only if not using regex (or if desired behavior is defined)
|
|
47
|
-
if whole_word:
|
|
49
|
+
if whole_word and not whole_line:
|
|
48
50
|
term = r'\b' + term + r'\b'
|
|
49
51
|
|
|
50
52
|
self.pattern = compile_regex(term, flags) # Precompile the regex pattern for performance
|
|
@@ -60,20 +62,29 @@ class TermNode(ExprNode):
|
|
|
60
62
|
if not self.fuzzy:
|
|
61
63
|
# Use substring search for more speed
|
|
62
64
|
if not self.regex and not self.whole_word:
|
|
65
|
+
if self.whole_line:
|
|
66
|
+
return term == text_cmp
|
|
63
67
|
return term in text_cmp
|
|
64
68
|
|
|
65
|
-
return bool(self.pattern.search(text))
|
|
69
|
+
return bool(self.pattern.search(text)) \
|
|
70
|
+
if not self.whole_line \
|
|
71
|
+
else bool(self.pattern.fullmatch(text))
|
|
72
|
+
|
|
73
|
+
if self.whole_line:
|
|
74
|
+
score = fuzz.ratio(term, text_cmp)
|
|
75
|
+
return score >= self.fuzzy_level
|
|
66
76
|
|
|
67
77
|
if self.whole_word:
|
|
68
78
|
words = re.findall(r'\w+', text_cmp)
|
|
69
79
|
return any(fuzz.ratio(term, word) >= self.fuzzy_level for word in words)
|
|
80
|
+
|
|
81
|
+
# Use the correct method depending on the len of str to increase accuracy and avoid illogical matching
|
|
82
|
+
if len(text_cmp) > len(term):
|
|
83
|
+
score = fuzz.partial_ratio(term, text_cmp)
|
|
70
84
|
else:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
else:
|
|
75
|
-
score = fuzz.ratio(term, text_cmp)
|
|
76
|
-
return score >= self.fuzzy_level
|
|
85
|
+
score = fuzz.ratio(term, text_cmp)
|
|
86
|
+
|
|
87
|
+
return score >= self.fuzzy_level
|
|
77
88
|
|
|
78
89
|
def get_binary_pattern(self):
|
|
79
90
|
"""
|
|
@@ -129,25 +140,8 @@ class OrNode(ExprNode):
|
|
|
129
140
|
return self.left.evaluate(text) or self.right.evaluate(text)
|
|
130
141
|
|
|
131
142
|
|
|
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
|
-
|
|
149
143
|
# Lark grammar for parsing logical expressions
|
|
150
|
-
query_grammar =
|
|
144
|
+
query_grammar = r"""
|
|
151
145
|
?start: expr
|
|
152
146
|
|
|
153
147
|
?expr: or_expr
|
|
@@ -165,7 +159,7 @@ query_grammar = rf"""
|
|
|
165
159
|
| ESCAPED_STRING -> string
|
|
166
160
|
| "(" expr ")"
|
|
167
161
|
|
|
168
|
-
PREFIXED_STRING: /
|
|
162
|
+
PREFIXED_STRING: /[rcwfpl]+"([^"\\]|\\.)*"/
|
|
169
163
|
|
|
170
164
|
%import common.ESCAPED_STRING
|
|
171
165
|
%import common.WS
|
|
@@ -204,6 +198,7 @@ class TreeToExpr(Transformer):
|
|
|
204
198
|
False,
|
|
205
199
|
False,
|
|
206
200
|
None,
|
|
201
|
+
False,
|
|
207
202
|
False
|
|
208
203
|
)
|
|
209
204
|
|
|
@@ -213,6 +208,14 @@ class TreeToExpr(Transformer):
|
|
|
213
208
|
|
|
214
209
|
quote_index = text.index('"')
|
|
215
210
|
prefix = text[:quote_index]
|
|
211
|
+
|
|
212
|
+
if len(prefix) != len(set(prefix)):
|
|
213
|
+
raise ValueError(f'Duplicate prefix flag: {prefix}')
|
|
214
|
+
if 'r' in prefix and 'f' in prefix:
|
|
215
|
+
raise ValueError('Regex and fuzzy prefixes cannot be combined.')
|
|
216
|
+
if 'r' in prefix and 'w' in prefix:
|
|
217
|
+
raise ValueError('Regex and whole-word prefixes cannot be combined. To use whole-word matching in regex, use \\b: r"\\btext\\b"')
|
|
218
|
+
|
|
216
219
|
quoted = text[quote_index:]
|
|
217
220
|
|
|
218
221
|
is_regex = 'r' in prefix
|
|
@@ -225,7 +228,8 @@ class TreeToExpr(Transformer):
|
|
|
225
228
|
case_sensitive='c' in prefix,
|
|
226
229
|
fuzzy='f' in prefix,
|
|
227
230
|
fuzzy_level=self.fuzzy_level,
|
|
228
|
-
full_path='p' in prefix
|
|
231
|
+
full_path='p' in prefix,
|
|
232
|
+
whole_line='l' in prefix
|
|
229
233
|
)
|
|
230
234
|
|
|
231
235
|
def and_expr(self, args):
|
|
@@ -238,7 +242,7 @@ class TreeToExpr(Transformer):
|
|
|
238
242
|
return NotNode(args[0])
|
|
239
243
|
|
|
240
244
|
|
|
241
|
-
def parse_query_expression(config) -> ExprNode:
|
|
245
|
+
def parse_query_expression(config) -> ExprNode | None:
|
|
242
246
|
"""
|
|
243
247
|
Function to parse the query and return expression tree.
|
|
244
248
|
If expr is False, treat the whole query as a single term.
|
|
@@ -255,7 +259,8 @@ def parse_query_expression(config) -> ExprNode:
|
|
|
255
259
|
config.case_sensitive,
|
|
256
260
|
config.fuzzy,
|
|
257
261
|
config.fuzzy_level,
|
|
258
|
-
config.full_path
|
|
262
|
+
config.full_path,
|
|
263
|
+
config.line
|
|
259
264
|
)
|
|
260
265
|
|
|
261
266
|
# Otherwise, parse using Lark
|
|
@@ -276,7 +281,7 @@ def find_term_matches(node: TermNode, text: str | Path, num: int) -> list[tuple[
|
|
|
276
281
|
|
|
277
282
|
if node.fuzzy:
|
|
278
283
|
# Fuzzy partial matching currently has no span extraction
|
|
279
|
-
if not node.whole_word:
|
|
284
|
+
if not node.whole_word and not node.whole_line:
|
|
280
285
|
return []
|
|
281
286
|
|
|
282
287
|
text_cmp = (
|
|
@@ -288,6 +293,11 @@ def find_term_matches(node: TermNode, text: str | Path, num: int) -> list[tuple[
|
|
|
288
293
|
else node.term_lower
|
|
289
294
|
)
|
|
290
295
|
|
|
296
|
+
if node.whole_line:
|
|
297
|
+
if fuzz.ratio(term, text_cmp) >= node.fuzzy_level:
|
|
298
|
+
return [(num, len(text) + num)]
|
|
299
|
+
return []
|
|
300
|
+
|
|
291
301
|
matches = []
|
|
292
302
|
|
|
293
303
|
for match in re.finditer(r'\w+', text_cmp):
|
|
@@ -301,6 +311,9 @@ def find_term_matches(node: TermNode, text: str | Path, num: int) -> list[tuple[
|
|
|
301
311
|
|
|
302
312
|
return matches
|
|
303
313
|
|
|
314
|
+
if node.whole_line and node.pattern.fullmatch(text) is not None:
|
|
315
|
+
return [(num, len(text) + num)]
|
|
316
|
+
|
|
304
317
|
return [
|
|
305
318
|
(match.start() + num, match.end() + num)
|
|
306
319
|
for match in node.pattern.finditer(text)
|
|
@@ -7,6 +7,8 @@ from itertools import islice
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from threading import RLock
|
|
9
9
|
|
|
10
|
+
import click
|
|
11
|
+
|
|
10
12
|
from pseek import _pignore
|
|
11
13
|
|
|
12
14
|
from .archive import ARCHIVE_EXTS, extract_names_from_archive, extract_text_from_archive
|
|
@@ -378,6 +380,20 @@ def is_symlink_valid(entry):
|
|
|
378
380
|
return True
|
|
379
381
|
|
|
380
382
|
|
|
383
|
+
def needs_ignore_matcher(config) -> bool:
|
|
384
|
+
# In cases where all paths must be searched and
|
|
385
|
+
# there are no filtering rules,
|
|
386
|
+
# there is no need to create matcher.
|
|
387
|
+
return (
|
|
388
|
+
not config.hidden
|
|
389
|
+
or not config.no_git_ignore
|
|
390
|
+
or not config.no_ignore_dot
|
|
391
|
+
or not config.no_ignore_exclude
|
|
392
|
+
or not config.no_ignore_global
|
|
393
|
+
or bool(config.glob)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
381
397
|
def walk_logic(path: Path, config, depth: int, matcher, ancestors: set):
|
|
382
398
|
"""Recursively walk a directory while pruning excluded subtrees"""
|
|
383
399
|
|
|
@@ -411,18 +427,27 @@ def walk_logic(path: Path, config, depth: int, matcher, ancestors: set):
|
|
|
411
427
|
continue
|
|
412
428
|
is_dir = False
|
|
413
429
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
if matcher is None:
|
|
431
|
+
ignored = False
|
|
432
|
+
whitelisted = False
|
|
433
|
+
should_descend = is_dir
|
|
434
|
+
else:
|
|
435
|
+
# Ignore rules / hidden filtering / glob logic
|
|
436
|
+
|
|
437
|
+
absolute_entry = (
|
|
438
|
+
Path(os.path.abspath(entry_path))
|
|
439
|
+
if not config.absolute_path
|
|
440
|
+
else entry_path
|
|
441
|
+
).as_posix()
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
ignored, whitelisted, should_descend = matcher.match_path(
|
|
445
|
+
absolute_entry,
|
|
446
|
+
# We don't need target of symlink; symlink itself is always a file
|
|
447
|
+
is_dir if not entry.is_symlink() else False,
|
|
448
|
+
)
|
|
449
|
+
except RuntimeError:
|
|
450
|
+
continue
|
|
426
451
|
|
|
427
452
|
if ignored:
|
|
428
453
|
continue
|
|
@@ -433,7 +458,12 @@ def walk_logic(path: Path, config, depth: int, matcher, ancestors: set):
|
|
|
433
458
|
# This flag is used in seek func to indicate that this dir
|
|
434
459
|
# doesn't need to be displayed, even though it must still
|
|
435
460
|
# be included in metric calculation.
|
|
436
|
-
metric_only =
|
|
461
|
+
metric_only = (
|
|
462
|
+
is_dir
|
|
463
|
+
and matcher is not None
|
|
464
|
+
and matcher.has_positive_globs
|
|
465
|
+
and not whitelisted
|
|
466
|
+
)
|
|
437
467
|
|
|
438
468
|
# A symlink may be a valid filename-search candidate even
|
|
439
469
|
# when its target is not followed or cannot be resolved.
|
|
@@ -462,6 +492,7 @@ def walk(config):
|
|
|
462
492
|
"""Include paths define traversal roots, avoiding unnecessary traversal
|
|
463
493
|
of unrelated parts of the tree"""
|
|
464
494
|
roots = config.include or {config.path}
|
|
495
|
+
needs_matcher = needs_ignore_matcher(config)
|
|
465
496
|
|
|
466
497
|
for root in roots:
|
|
467
498
|
depth = len(root.relative_to(config.path).parts) - 1
|
|
@@ -473,18 +504,27 @@ def walk(config):
|
|
|
473
504
|
yield root.resolve(), False
|
|
474
505
|
|
|
475
506
|
if root.is_dir() and any(depth < d[1] for d in config.depth):
|
|
476
|
-
matcher =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
507
|
+
matcher = None
|
|
508
|
+
|
|
509
|
+
if needs_matcher:
|
|
510
|
+
try:
|
|
511
|
+
matcher = _pignore.IgnoreMatcher(
|
|
512
|
+
Path(os.path.abspath(root)).as_posix(),
|
|
513
|
+
git_ignore=not config.no_git_ignore,
|
|
514
|
+
ignore=not config.no_ignore_dot,
|
|
515
|
+
git_exclude=not config.no_ignore_exclude,
|
|
516
|
+
git_global=not config.no_ignore_global,
|
|
517
|
+
parents=not config.no_ignore_parent,
|
|
518
|
+
require_git=not config.no_require_git,
|
|
519
|
+
hidden=not config.hidden,
|
|
520
|
+
globs=list(config.glob) or None,
|
|
521
|
+
glob_root=config.path.as_posix()
|
|
522
|
+
)
|
|
523
|
+
except ValueError as e:
|
|
524
|
+
raise click.BadParameter(e, param_hint='--glob')
|
|
525
|
+
except RuntimeError as e:
|
|
526
|
+
click.secho(e, fg='red')
|
|
527
|
+
raise click.exceptions.Exit(1)
|
|
488
528
|
|
|
489
529
|
yield from walk_logic(root, config, depth, matcher, set())
|
|
490
530
|
|
|
@@ -24,7 +24,7 @@ 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, is_dir: bool | None = None) -> str:
|
|
27
|
+
def get_path_suffix(path: Path | str, is_dir: bool | None = None) -> str | None:
|
|
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)
|
|
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
|