pseek 3.6.2__tar.gz → 3.7.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pseek
3
- Version: 3.6.2
3
+ Version: 3.7.0
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"` |
@@ -399,6 +402,39 @@ With `--full-path`, you can match `fd/f` against the entry `fd/file.txt` inside
399
402
 
400
403
  > **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
404
 
405
+ ### Line
406
+
407
+ Match the entire line or selected path instead of matching a pattern anywhere within it.
408
+
409
+ ```bash
410
+ # Match lines exactly equal to "TODO"
411
+ psk TODO --line
412
+
413
+ # Match only lines containing exactly "foo" or "bar"
414
+ psk 'foo|bar' --regex --line
415
+
416
+ # Match files whose names are exactly "cli.py"
417
+ psk "cli.py" --file --line
418
+ ```
419
+
420
+ > **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.
421
+
422
+ By default, Pseek matches a file's or directory's **name**. Use `--full-path` to match against its full path instead.
423
+
424
+ ```bash
425
+ # Match files named "cli.py"
426
+ psk "cli.py" --file --line
427
+
428
+ # Match the full relative path "pseek/cli.py"
429
+ psk "pseek/cli.py" --file --full-path --line
430
+
431
+ # Match the full absolute path
432
+ psk "/home/python/cli/pseek/utils.py" \
433
+ --file --full-path --absolute-path --line
434
+ ```
435
+
436
+ > **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.
437
+
402
438
  ## Expression Queries
403
439
 
404
440
  Expression mode enables logical search expressions.
@@ -453,9 +489,7 @@ Each term can have its own search mode.
453
489
 
454
490
  #### Regex
455
491
 
456
- ```text
457
- r"pattern"
458
- ```
492
+ Usage: `r"pattern"`
459
493
 
460
494
  Example:
461
495
 
@@ -465,9 +499,7 @@ psk 'r"error\d+"' --expr
465
499
 
466
500
  #### Case Sensitive
467
501
 
468
- ```text
469
- c"text"
470
- ```
502
+ Usage: `c"text"`
471
503
 
472
504
  Example:
473
505
 
@@ -477,9 +509,7 @@ psk 'c"Error"' --expr
477
509
 
478
510
  #### Whole Word
479
511
 
480
- ```text
481
- w"text"
482
- ```
512
+ Usage: `w"text"`
483
513
 
484
514
  Example:
485
515
 
@@ -489,9 +519,7 @@ psk 'w"cat"' --expr
489
519
 
490
520
  #### Fuzzy
491
521
 
492
- ```text
493
- f"text"
494
- ```
522
+ Usage: `f"text"`
495
523
 
496
524
  Example:
497
525
 
@@ -501,9 +529,7 @@ psk 'f"apple"' --expr
501
529
 
502
530
  #### Full Path
503
531
 
504
- ```text
505
- p"text"
506
- ```
532
+ Usage: `p"text"`
507
533
 
508
534
  Example:
509
535
 
@@ -511,6 +537,16 @@ Example:
511
537
  psk 'p"lib"' --expr
512
538
  ```
513
539
 
540
+ #### Line
541
+
542
+ Usage: `l"text"`
543
+
544
+ Example:
545
+
546
+ ```bash
547
+ psk 'l"foo"' --expr
548
+ ```
549
+
514
550
  #### Combined Prefixes
515
551
 
516
552
  Prefixes can be combined.
@@ -538,9 +574,7 @@ Meaning:
538
574
 
539
575
  simultaneously.
540
576
 
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.
577
+ All modes are permitted, except for those where regex and word, or regex and fuzzy, are used simultaneously.
544
578
 
545
579
  > **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
580
 
@@ -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"` |
@@ -371,6 +374,39 @@ With `--full-path`, you can match `fd/f` against the entry `fd/file.txt` inside
371
374
 
372
375
  > **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
376
 
377
+ ### Line
378
+
379
+ Match the entire line or selected path instead of matching a pattern anywhere within it.
380
+
381
+ ```bash
382
+ # Match lines exactly equal to "TODO"
383
+ psk TODO --line
384
+
385
+ # Match only lines containing exactly "foo" or "bar"
386
+ psk 'foo|bar' --regex --line
387
+
388
+ # Match files whose names are exactly "cli.py"
389
+ psk "cli.py" --file --line
390
+ ```
391
+
392
+ > **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.
393
+
394
+ By default, Pseek matches a file's or directory's **name**. Use `--full-path` to match against its full path instead.
395
+
396
+ ```bash
397
+ # Match files named "cli.py"
398
+ psk "cli.py" --file --line
399
+
400
+ # Match the full relative path "pseek/cli.py"
401
+ psk "pseek/cli.py" --file --full-path --line
402
+
403
+ # Match the full absolute path
404
+ psk "/home/python/cli/pseek/utils.py" \
405
+ --file --full-path --absolute-path --line
406
+ ```
407
+
408
+ > **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.
409
+
374
410
  ## Expression Queries
375
411
 
376
412
  Expression mode enables logical search expressions.
@@ -425,9 +461,7 @@ Each term can have its own search mode.
425
461
 
426
462
  #### Regex
427
463
 
428
- ```text
429
- r"pattern"
430
- ```
464
+ Usage: `r"pattern"`
431
465
 
432
466
  Example:
433
467
 
@@ -437,9 +471,7 @@ psk 'r"error\d+"' --expr
437
471
 
438
472
  #### Case Sensitive
439
473
 
440
- ```text
441
- c"text"
442
- ```
474
+ Usage: `c"text"`
443
475
 
444
476
  Example:
445
477
 
@@ -449,9 +481,7 @@ psk 'c"Error"' --expr
449
481
 
450
482
  #### Whole Word
451
483
 
452
- ```text
453
- w"text"
454
- ```
484
+ Usage: `w"text"`
455
485
 
456
486
  Example:
457
487
 
@@ -461,9 +491,7 @@ psk 'w"cat"' --expr
461
491
 
462
492
  #### Fuzzy
463
493
 
464
- ```text
465
- f"text"
466
- ```
494
+ Usage: `f"text"`
467
495
 
468
496
  Example:
469
497
 
@@ -473,9 +501,7 @@ psk 'f"apple"' --expr
473
501
 
474
502
  #### Full Path
475
503
 
476
- ```text
477
- p"text"
478
- ```
504
+ Usage: `p"text"`
479
505
 
480
506
  Example:
481
507
 
@@ -483,6 +509,16 @@ Example:
483
509
  psk 'p"lib"' --expr
484
510
  ```
485
511
 
512
+ #### Line
513
+
514
+ Usage: `l"text"`
515
+
516
+ Example:
517
+
518
+ ```bash
519
+ psk 'l"foo"' --expr
520
+ ```
521
+
486
522
  #### Combined Prefixes
487
523
 
488
524
  Prefixes can be combined.
@@ -510,9 +546,7 @@ Meaning:
510
546
 
511
547
  simultaneously.
512
548
 
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.
549
+ All modes are permitted, except for those where regex and word, or regex and fuzzy, are used simultaneously.
516
550
 
517
551
  > **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
552
 
@@ -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: Fuzzy substring highlighting and counting matches are disabled to improve performance.",
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
- # Use the correct method depending on the len of str to increase accuracy and avoid illogical matching
72
- if len(text_cmp) > len(term):
73
- score = fuzz.partial_ratio(term, text_cmp)
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 = rf"""
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: /({gvp()})"([^"\\]|\\.)*"/
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)
@@ -133,6 +133,7 @@ class SearchConfig:
133
133
  case_sensitive: bool
134
134
  regex: bool
135
135
  word: bool
136
+ line: bool
136
137
  expr: bool
137
138
  depth: list[tuple]
138
139
  timeout: int | None
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pseek"
7
- version = "3.6.2"
7
+ version = "3.7.0"
8
8
  description = "Pseek is a Python CLI toolkit to search files, folders, and text"
9
9
  readme = "README.md"
10
10
  license = "MIT"
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