rgapi 0.1.0__tar.gz → 0.1.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.
- {rgapi-0.1.0 → rgapi-0.1.2}/.github/workflows/ci.yml +5 -4
- {rgapi-0.1.0 → rgapi-0.1.2}/.gitignore +1 -0
- {rgapi-0.1.0 → rgapi-0.1.2}/Cargo.lock +1 -1
- {rgapi-0.1.0 → rgapi-0.1.2}/Cargo.toml +1 -1
- {rgapi-0.1.0 → rgapi-0.1.2}/PKG-INFO +3 -2
- {rgapi-0.1.0 → rgapi-0.1.2}/README.md +2 -1
- {rgapi-0.1.0 → rgapi-0.1.2}/pyproject.toml +7 -1
- rgapi-0.1.2/python/rgapi/__init__.py +211 -0
- {rgapi-0.1.0 → rgapi-0.1.2}/src/python.rs +40 -10
- {rgapi-0.1.0 → rgapi-0.1.2}/src/search.rs +91 -7
- {rgapi-0.1.0 → rgapi-0.1.2}/tests/test_rgapi.py +28 -0
- rgapi-0.1.0/python/rgapi/__init__.py +0 -106
- rgapi-0.1.0/tools/build.sh +0 -5
- rgapi-0.1.0/tools/bump.sh +0 -6
- rgapi-0.1.0/tools/bump2.sh +0 -6
- rgapi-0.1.0/tools/release.sh +0 -6
- rgapi-0.1.0/tools/test.sh +0 -4
- {rgapi-0.1.0 → rgapi-0.1.2}/DEV.md +0 -0
- {rgapi-0.1.0 → rgapi-0.1.2}/src/lib.rs +0 -0
- {rgapi-0.1.0 → rgapi-0.1.2}/src/walk.rs +0 -0
- {rgapi-0.1.0 → rgapi-0.1.2}/tools/bench.py +0 -0
|
@@ -10,7 +10,7 @@ jobs:
|
|
|
10
10
|
test:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
14
|
- run: cargo test
|
|
15
15
|
|
|
16
16
|
build:
|
|
@@ -21,12 +21,13 @@ jobs:
|
|
|
21
21
|
python: ['3.10', '3.11', '3.12', '3.13']
|
|
22
22
|
runs-on: ${{ matrix.os }}
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
25
|
- uses: dtolnay/rust-toolchain@stable
|
|
26
26
|
- uses: actions/setup-python@v5
|
|
27
27
|
with:
|
|
28
28
|
python-version: ${{ matrix.python }}
|
|
29
|
-
- run:
|
|
29
|
+
- run: pip install fastship
|
|
30
|
+
- run: ship-rs-prep --release
|
|
30
31
|
- uses: PyO3/maturin-action@v1
|
|
31
32
|
with:
|
|
32
33
|
command: build
|
|
@@ -44,7 +45,7 @@ jobs:
|
|
|
44
45
|
id-token: write
|
|
45
46
|
contents: write
|
|
46
47
|
steps:
|
|
47
|
-
- uses: actions/checkout@
|
|
48
|
+
- uses: actions/checkout@v6
|
|
48
49
|
- uses: PyO3/maturin-action@v1
|
|
49
50
|
with:
|
|
50
51
|
command: sdist
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rgapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Summary: Python API for ripgrep-style file walking and searching
|
|
@@ -54,6 +54,7 @@ pip install rgapi
|
|
|
54
54
|
## Semantics
|
|
55
55
|
|
|
56
56
|
`fd` and `walk` return slash-separated paths relative to `root`. They use the `ignore` crate, so `.gitignore` and the usual ripgrep filters apply by default. Hidden files are skipped unless `hidden=True`. Pass `ignore=False` to disable ignore filtering. Symlinks are not followed unless `follow_links=True`; `same_file_system=True` avoids crossing filesystem boundaries. Traversal is parallel, and result order is not guaranteed; use `sorted(...)` if order matters.
|
|
57
|
+
`root` arguments accept `str` or `pathlib.Path` and expand `~`; `search_path` also accepts path-like file paths. Display labels such as `display_path` are stringified without expansion.
|
|
57
58
|
|
|
58
59
|
`fd` adds fd-like filtering on top of `walk`: `pattern` is a substring match on the relative path, and `include`/`exclude` use glob syntax. `glob=` is accepted as an alias for `include=`. A basename glob such as `*.py` also matches recursively, so it finds `src/app.py`. Use `ext="py"` or `ext=["py", "rs"]` for extension filters, `min_depth=`/`max_depth=` to bound recursion, and `max_filesize=` to skip files above a byte limit.
|
|
59
60
|
|
|
@@ -73,7 +74,7 @@ matches list of (start, end) byte offsets for match rows
|
|
|
73
74
|
|
|
74
75
|
`SearchLine` has a structured `repr`, an rg-style `str`, and `SearchLine.asdict()` returns row fields as a plain Python dict. `rg(..., paths=True)` returns unique matched paths, and `rg(..., count=True)` returns the total number of match spans. `paths` and `count` cannot both be set.
|
|
75
76
|
|
|
76
|
-
`before_context`, `after_context`, and `context`
|
|
77
|
+
`before_context`, `after_context`, and `context` are like `rg -B`, `rg -A`, and `rg -C`. Files containing NUL bytes or invalid UTF-8 are skipped.
|
|
77
78
|
|
|
78
79
|
Search is case-sensitive by default, matching `rg`. Use `smart_case=True` for `rg --smart-case` behavior, or `case_sensitive=False` to force case-insensitive matching.
|
|
79
80
|
|
|
@@ -39,6 +39,7 @@ pip install rgapi
|
|
|
39
39
|
## Semantics
|
|
40
40
|
|
|
41
41
|
`fd` and `walk` return slash-separated paths relative to `root`. They use the `ignore` crate, so `.gitignore` and the usual ripgrep filters apply by default. Hidden files are skipped unless `hidden=True`. Pass `ignore=False` to disable ignore filtering. Symlinks are not followed unless `follow_links=True`; `same_file_system=True` avoids crossing filesystem boundaries. Traversal is parallel, and result order is not guaranteed; use `sorted(...)` if order matters.
|
|
42
|
+
`root` arguments accept `str` or `pathlib.Path` and expand `~`; `search_path` also accepts path-like file paths. Display labels such as `display_path` are stringified without expansion.
|
|
42
43
|
|
|
43
44
|
`fd` adds fd-like filtering on top of `walk`: `pattern` is a substring match on the relative path, and `include`/`exclude` use glob syntax. `glob=` is accepted as an alias for `include=`. A basename glob such as `*.py` also matches recursively, so it finds `src/app.py`. Use `ext="py"` or `ext=["py", "rs"]` for extension filters, `min_depth=`/`max_depth=` to bound recursion, and `max_filesize=` to skip files above a byte limit.
|
|
44
45
|
|
|
@@ -58,7 +59,7 @@ matches list of (start, end) byte offsets for match rows
|
|
|
58
59
|
|
|
59
60
|
`SearchLine` has a structured `repr`, an rg-style `str`, and `SearchLine.asdict()` returns row fields as a plain Python dict. `rg(..., paths=True)` returns unique matched paths, and `rg(..., count=True)` returns the total number of match spans. `paths` and `count` cannot both be set.
|
|
60
61
|
|
|
61
|
-
`before_context`, `after_context`, and `context`
|
|
62
|
+
`before_context`, `after_context`, and `context` are like `rg -B`, `rg -A`, and `rg -C`. Files containing NUL bytes or invalid UTF-8 are skipped.
|
|
62
63
|
|
|
63
64
|
Search is case-sensitive by default, matching `rg`. Use `smart_case=True` for `rg --smart-case` behavior, or `case_sensitive=False` to force case-insensitive matching.
|
|
64
65
|
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rgapi"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Python API for ripgrep-style file walking and searching"
|
|
9
9
|
license = {text = "MIT OR Apache-2.0"}
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -24,3 +24,9 @@ Issues = "https://github.com/AnswerDotAI/rgapi/issues"
|
|
|
24
24
|
features = ["extension-module"]
|
|
25
25
|
python-source = "python"
|
|
26
26
|
module-name = "rgapi._core"
|
|
27
|
+
|
|
28
|
+
[tool.fastship]
|
|
29
|
+
branch = "main"
|
|
30
|
+
|
|
31
|
+
[tool.fastship.rs]
|
|
32
|
+
bins = []
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from os import fspath
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from . import _core
|
|
5
|
+
|
|
6
|
+
Regex = _core.Regex
|
|
7
|
+
SearchLine = _core.SearchLine
|
|
8
|
+
RgIter = _core.RgIter
|
|
9
|
+
def compile(
|
|
10
|
+
pattern:str, # Regex pattern to compile
|
|
11
|
+
case_sensitive:bool|None=None, # True/False forces case; None allows `smart_case`
|
|
12
|
+
smart_case:bool=False # Match `rg --smart-case` behavior
|
|
13
|
+
) -> Regex:
|
|
14
|
+
"Compile a regex matcher for `search_text`, `search_path`, and direct matching."
|
|
15
|
+
return _core.compile(pattern, case_sensitive=case_sensitive, smart_case=smart_case)
|
|
16
|
+
|
|
17
|
+
class SearchResults(list):
|
|
18
|
+
"List of `SearchLine` rows with rg-style text display."
|
|
19
|
+
def __str__(self): return "\n".join(map(str, self))
|
|
20
|
+
def _repr_pretty_(self, p, cycle): p.text("..." if cycle else str(self))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _listify(value):
|
|
24
|
+
if value is None: return []
|
|
25
|
+
if isinstance(value, str): return [value]
|
|
26
|
+
return list(value)
|
|
27
|
+
def _fs_path(path): return str(Path(path).expanduser())
|
|
28
|
+
def _display_path(path): return None if path is None else fspath(path)
|
|
29
|
+
def _filters(glob=None, include=None, exclude=None, ext=None):
|
|
30
|
+
includes = _listify(include) + _listify(glob)
|
|
31
|
+
for suffix in _listify(ext):
|
|
32
|
+
suffix = str(suffix)
|
|
33
|
+
if suffix.startswith("."): suffix = suffix[1:]
|
|
34
|
+
includes.append(f"*.{suffix}")
|
|
35
|
+
return includes, _listify(exclude)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _context(context, before_context, after_context):
|
|
39
|
+
if context: return context, context
|
|
40
|
+
return before_context, after_context
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def walk(
|
|
44
|
+
root:str|Path=".", # Directory to walk (expands `~`)
|
|
45
|
+
hidden:bool=False, # Include hidden files and directories
|
|
46
|
+
ignore:bool=True, # Respect `.gitignore` and other ignore files
|
|
47
|
+
max_depth:int|None=None, # Maximum directory depth to descend
|
|
48
|
+
min_depth:int|None=None, # Minimum depth for returned paths
|
|
49
|
+
max_filesize:int|None=None, # Skip files larger than this many bytes
|
|
50
|
+
follow_links:bool=False, # Follow symbolic links while walking
|
|
51
|
+
same_file_system:bool=False, # Do not cross filesystem boundaries
|
|
52
|
+
path_re:str|None=None, # Regex that returned relative paths must match
|
|
53
|
+
skip_path_re:str|None=None, # Regex for relative paths to skip
|
|
54
|
+
skip_dir=None, # Directory glob or globs to prune
|
|
55
|
+
skip_dir_re:str|None=None, # Directory regex used to prune traversal
|
|
56
|
+
files:bool=True, # Include files in results
|
|
57
|
+
dirs:bool=False # Include directories in results
|
|
58
|
+
) -> list[str]:
|
|
59
|
+
"Walk a directory and return relative file and/or directory paths."
|
|
60
|
+
return _core.walk(_fs_path(root), hidden, ignore, max_depth, min_depth, max_filesize, follow_links,
|
|
61
|
+
same_file_system, path_re, skip_path_re, _listify(skip_dir), skip_dir_re, files, dirs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def fd(
|
|
65
|
+
root:str|Path=".", # Directory to walk (expands `~`)
|
|
66
|
+
pattern:str|None=None, # Substring that relative paths must contain
|
|
67
|
+
glob=None, # Include glob or globs; alias for `include`
|
|
68
|
+
include=None, # Include glob or globs, e.g. `*.py`
|
|
69
|
+
exclude=None, # Exclude glob or globs, e.g. `test_*.py`
|
|
70
|
+
ext=None, # Extension or extensions to include, without needing `*.`
|
|
71
|
+
hidden:bool=False, # Include hidden files and directories
|
|
72
|
+
ignore:bool=True, # Respect `.gitignore` and other ignore files
|
|
73
|
+
max_depth:int|None=None, # Maximum directory depth to descend
|
|
74
|
+
min_depth:int|None=None, # Minimum depth for returned paths
|
|
75
|
+
max_filesize:int|None=None, # Skip files larger than this many bytes
|
|
76
|
+
follow_links:bool=False, # Follow symbolic links while walking
|
|
77
|
+
same_file_system:bool=False, # Do not cross filesystem boundaries
|
|
78
|
+
path_re:str|None=None, # Regex that returned relative paths must match
|
|
79
|
+
skip_path_re:str|None=None, # Regex for relative paths to skip
|
|
80
|
+
skip_dir=None, # Directory glob or globs to prune
|
|
81
|
+
skip_dir_re:str|None=None, # Directory regex used to prune traversal
|
|
82
|
+
files:bool=True, # Include files in results
|
|
83
|
+
dirs:bool=False # Include directories in results
|
|
84
|
+
) -> list[str]:
|
|
85
|
+
"Find paths with fd-style filters and gitignore support."
|
|
86
|
+
include, exclude = _filters(glob, include, exclude, ext)
|
|
87
|
+
return _core.find(_fs_path(root), pattern, include, exclude, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
88
|
+
follow_links, same_file_system, path_re, skip_path_re, _listify(skip_dir), skip_dir_re, files, dirs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
92
|
+
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
93
|
+
before_context, after_context, context):
|
|
94
|
+
include, exclude = _filters(glob, include, exclude, ext)
|
|
95
|
+
before_context, after_context = _context(context, before_context, after_context)
|
|
96
|
+
return (pattern, _fs_path(root), include, exclude, hidden, ignore, max_depth, min_depth, max_filesize, follow_links, same_file_system,
|
|
97
|
+
path_re, skip_path_re, _listify(skip_dir), skip_dir_re, case_sensitive, smart_case, before_context, after_context)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def rg(
|
|
101
|
+
pattern:str, # Regex pattern to search for
|
|
102
|
+
root:str|Path=".", # Directory to search (expands `~`)
|
|
103
|
+
glob=None, # Include glob or globs; alias for `include`
|
|
104
|
+
include=None, # Include glob or globs, e.g. `*.py`
|
|
105
|
+
exclude=None, # Exclude glob or globs, e.g. `test_*.py`
|
|
106
|
+
ext=None, # Extension or extensions to include, without needing `*.`
|
|
107
|
+
hidden:bool=False, # Include hidden files and directories
|
|
108
|
+
ignore:bool=True, # Respect `.gitignore` and other ignore files
|
|
109
|
+
max_depth:int|None=None, # Maximum directory depth to descend
|
|
110
|
+
min_depth:int|None=None, # Minimum depth for returned/searched files
|
|
111
|
+
max_filesize:int|None=None, # Skip files larger than this many bytes
|
|
112
|
+
follow_links:bool=False, # Follow symbolic links while walking
|
|
113
|
+
same_file_system:bool=False, # Do not cross filesystem boundaries
|
|
114
|
+
path_re:str|None=None, # Regex that searched relative paths must match
|
|
115
|
+
skip_path_re:str|None=None, # Regex for relative paths to skip
|
|
116
|
+
skip_dir=None, # Directory glob or globs to prune
|
|
117
|
+
skip_dir_re:str|None=None, # Directory regex used to prune traversal
|
|
118
|
+
case_sensitive:bool|None=None, # True/False forces case; None allows `smart_case`
|
|
119
|
+
smart_case:bool=False, # Match `rg --smart-case` behavior
|
|
120
|
+
before_context:int=0, # Lines of context before each match, like `rg -B`
|
|
121
|
+
after_context:int=0, # Lines of context after each match, like `rg -A`
|
|
122
|
+
context:int=0, # Sets both before and after context, like `rg -C`
|
|
123
|
+
paths:bool=False, # Return unique matched paths instead of rows
|
|
124
|
+
count:bool=False # Return total match span count instead of rows
|
|
125
|
+
):
|
|
126
|
+
"Search files and return `SearchResults`, matched paths, or a count."
|
|
127
|
+
assert not (paths and count), "paths and count are mutually exclusive"
|
|
128
|
+
args = _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
129
|
+
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
130
|
+
before_context, after_context, context)
|
|
131
|
+
if paths:
|
|
132
|
+
seen, res = set(), []
|
|
133
|
+
for row in _core.rg_iter(*args):
|
|
134
|
+
if row.kind != "match" or row.path in seen: continue
|
|
135
|
+
seen.add(row.path)
|
|
136
|
+
res.append(row.path)
|
|
137
|
+
return res
|
|
138
|
+
if count: return sum(len(row.matches) for row in _core.rg_iter(*args) if row.kind == "match")
|
|
139
|
+
return SearchResults(_core.rg(*args))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def rg_iter(
|
|
143
|
+
pattern:str, # Regex pattern to search for
|
|
144
|
+
root:str|Path=".", # Directory to search (expands `~`)
|
|
145
|
+
glob=None, # Include glob or globs; alias for `include`
|
|
146
|
+
include=None, # Include glob or globs, e.g. `*.py`
|
|
147
|
+
exclude=None, # Exclude glob or globs, e.g. `test_*.py`
|
|
148
|
+
ext=None, # Extension or extensions to include, without needing `*.`
|
|
149
|
+
hidden:bool=False, # Include hidden files and directories
|
|
150
|
+
ignore:bool=True, # Respect `.gitignore` and other ignore files
|
|
151
|
+
max_depth:int|None=None, # Maximum directory depth to descend
|
|
152
|
+
min_depth:int|None=None, # Minimum depth for returned/searched files
|
|
153
|
+
max_filesize:int|None=None, # Skip files larger than this many bytes
|
|
154
|
+
follow_links:bool=False, # Follow symbolic links while walking
|
|
155
|
+
same_file_system:bool=False, # Do not cross filesystem boundaries
|
|
156
|
+
path_re:str|None=None, # Regex that searched relative paths must match
|
|
157
|
+
skip_path_re:str|None=None, # Regex for relative paths to skip
|
|
158
|
+
skip_dir=None, # Directory glob or globs to prune
|
|
159
|
+
skip_dir_re:str|None=None, # Directory regex used to prune traversal
|
|
160
|
+
case_sensitive:bool|None=None, # True/False forces case; None allows `smart_case`
|
|
161
|
+
smart_case:bool=False, # Match `rg --smart-case` behavior
|
|
162
|
+
before_context:int=0, # Lines of context before each match, like `rg -B`
|
|
163
|
+
after_context:int=0, # Lines of context after each match, like `rg -A`
|
|
164
|
+
context:int=0 # Sets both before and after context, like `rg -C`
|
|
165
|
+
) -> RgIter:
|
|
166
|
+
"Search files lazily, yielding `SearchLine` rows."
|
|
167
|
+
args = _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
168
|
+
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
169
|
+
before_context, after_context, context)
|
|
170
|
+
return _core.rg_iter(*args)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def search_text(
|
|
174
|
+
matcher:Regex, # Compiled `Regex` from `compile()`
|
|
175
|
+
text:str, # Text to search
|
|
176
|
+
path:str|Path="<text>", # Path label stored in results
|
|
177
|
+
before_context:int=0, # Lines of context before each match
|
|
178
|
+
after_context:int=0, # Lines of context after each match
|
|
179
|
+
context:int=0 # Sets both before and after context, like `rg -C`
|
|
180
|
+
) -> SearchResults:
|
|
181
|
+
"Search an in-memory string with a compiled matcher."
|
|
182
|
+
before_context, after_context = _context(context, before_context, after_context)
|
|
183
|
+
return SearchResults(_core.search_text(matcher, text, _display_path(path), before_context, after_context))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def search_path(
|
|
187
|
+
matcher:Regex, # Compiled `Regex` from `compile()`
|
|
188
|
+
path:str|Path, # File path to search (expands `~`)
|
|
189
|
+
display_path:str|Path|None=None, # Path stored in results; defaults to `path`
|
|
190
|
+
before_context:int=0, # Lines of context before each match
|
|
191
|
+
after_context:int=0, # Lines of context after each match
|
|
192
|
+
context:int=0 # Sets both before and after context, like `rg -C`
|
|
193
|
+
) -> SearchResults:
|
|
194
|
+
"Search one file with a compiled matcher."
|
|
195
|
+
before_context, after_context = _context(context, before_context, after_context)
|
|
196
|
+
return SearchResults(_core.search_path(matcher, _fs_path(path), _display_path(display_path), before_context, after_context))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = [
|
|
200
|
+
"Regex",
|
|
201
|
+
"RgIter",
|
|
202
|
+
"SearchLine",
|
|
203
|
+
"SearchResults",
|
|
204
|
+
"compile",
|
|
205
|
+
"fd",
|
|
206
|
+
"rg",
|
|
207
|
+
"rg_iter",
|
|
208
|
+
"search_path",
|
|
209
|
+
"search_text",
|
|
210
|
+
"walk",
|
|
211
|
+
]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
use grep_matcher::Matcher;
|
|
2
2
|
use std::path::PathBuf;
|
|
3
|
+
use std::sync::mpsc::RecvTimeoutError;
|
|
4
|
+
use std::time::Duration;
|
|
3
5
|
|
|
4
6
|
use pyo3::exceptions::PyValueError;
|
|
5
7
|
use pyo3::prelude::*;
|
|
@@ -7,7 +9,7 @@ use pyo3::types::{PyAny, PyDict};
|
|
|
7
9
|
|
|
8
10
|
use crate::search::spans_for;
|
|
9
11
|
use crate::{
|
|
10
|
-
compile_regex, find,
|
|
12
|
+
compile_regex, find, rg_iter as rg_iter_core, search_path as search_path_core,
|
|
11
13
|
search_text as search_text_core, FindOptions, RgIter, RgOptions, SearchLine,
|
|
12
14
|
};
|
|
13
15
|
|
|
@@ -70,12 +72,11 @@ impl RgIterPy {
|
|
|
70
72
|
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
|
|
71
73
|
slf
|
|
72
74
|
}
|
|
73
|
-
fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult<Option<SearchLinePy>> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
75
|
+
fn __next__(mut slf: PyRefMut<'_, Self>, py: Python<'_>) -> PyResult<Option<SearchLinePy>> {
|
|
76
|
+
next_rg_line_py(py, &mut slf.inner)
|
|
77
|
+
}
|
|
78
|
+
fn cancel(&self) {
|
|
79
|
+
self.inner.cancel();
|
|
79
80
|
}
|
|
80
81
|
fn __repr__(&self) -> String {
|
|
81
82
|
"RgIter(SearchLine stream)".to_string()
|
|
@@ -93,6 +94,35 @@ impl RgIterPy {
|
|
|
93
94
|
Ok(())
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
fn check_signals_or_cancel(py: Python<'_>, iter: &RgIter) -> PyResult<()> {
|
|
98
|
+
if let Err(err) = py.check_signals() {
|
|
99
|
+
iter.cancel();
|
|
100
|
+
return Err(err);
|
|
101
|
+
}
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn next_rg_line_py(py: Python<'_>, iter: &mut RgIter) -> PyResult<Option<SearchLinePy>> {
|
|
106
|
+
loop {
|
|
107
|
+
check_signals_or_cancel(py, iter)?;
|
|
108
|
+
let res = py.allow_threads(|| iter.next_timeout(Duration::from_millis(50)));
|
|
109
|
+
check_signals_or_cancel(py, iter)?;
|
|
110
|
+
match res {
|
|
111
|
+
Ok(Ok(line)) => return Ok(Some(SearchLinePy::from(line))),
|
|
112
|
+
Ok(Err(err)) => return Err(PyValueError::new_err(err.to_string())),
|
|
113
|
+
Err(RecvTimeoutError::Disconnected) => return Ok(None),
|
|
114
|
+
Err(RecvTimeoutError::Timeout) => continue,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn collect_rg_py(py: Python<'_>, mut iter: RgIter) -> PyResult<Vec<SearchLinePy>> {
|
|
120
|
+
let mut res = Vec::new();
|
|
121
|
+
while let Some(line) = next_rg_line_py(py, &mut iter)? {
|
|
122
|
+
res.push(line);
|
|
123
|
+
}
|
|
124
|
+
Ok(res)
|
|
125
|
+
}
|
|
96
126
|
#[pyclass(name = "Regex")]
|
|
97
127
|
#[derive(Clone)]
|
|
98
128
|
struct RegexPy {
|
|
@@ -294,6 +324,7 @@ fn search_path_py(
|
|
|
294
324
|
#[pyfunction(name = "rg")]
|
|
295
325
|
#[pyo3(signature = (pattern, root=".", include=None, exclude=None, hidden=false, ignore=true, max_depth=None, min_depth=None, max_filesize=None, follow_links=false, same_file_system=false, path_re=None, skip_path_re=None, skip_dir=None, skip_dir_re=None, case_sensitive=None, smart_case=false, before_context=0, after_context=0))]
|
|
296
326
|
fn rg_py(
|
|
327
|
+
py: Python<'_>,
|
|
297
328
|
pattern: String,
|
|
298
329
|
root: &str,
|
|
299
330
|
include: Option<Vec<String>>,
|
|
@@ -335,9 +366,8 @@ fn rg_py(
|
|
|
335
366
|
before_context,
|
|
336
367
|
after_context,
|
|
337
368
|
};
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
.map_err(|e| PyValueError::new_err(e.to_string()))
|
|
369
|
+
let iter = rg_iter_core(&opts).map_err(|e| PyValueError::new_err(e.to_string()))?;
|
|
370
|
+
collect_rg_py(py, iter)
|
|
341
371
|
}
|
|
342
372
|
#[pyfunction(name = "rg_iter")]
|
|
343
373
|
#[pyo3(signature = (pattern, root=".", include=None, exclude=None, hidden=false, ignore=true, max_depth=None, min_depth=None, max_filesize=None, follow_links=false, same_file_system=false, path_re=None, skip_path_re=None, skip_dir=None, skip_dir_re=None, case_sensitive=None, smart_case=false, before_context=0, after_context=0))]
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
use std::path::{Path, PathBuf};
|
|
2
|
-
use std::sync::
|
|
3
|
-
|
|
2
|
+
use std::sync::{
|
|
3
|
+
atomic::{AtomicBool, Ordering},
|
|
4
|
+
mpsc::{self, Receiver, RecvTimeoutError, Sender},
|
|
5
|
+
Arc,
|
|
6
|
+
};
|
|
7
|
+
use std::time::Duration;
|
|
4
8
|
|
|
5
9
|
use grep_matcher::Matcher;
|
|
6
10
|
use grep_regex::{RegexMatcher, RegexMatcherBuilder};
|
|
@@ -109,25 +113,49 @@ pub fn rg_iter(opts: &RgOptions) -> Result<RgIter, RgApiError> {
|
|
|
109
113
|
)?);
|
|
110
114
|
let matcher = compile_regex(&opts.pattern, opts.case_sensitive, opts.smart_case)?;
|
|
111
115
|
let opts = opts.clone();
|
|
116
|
+
let cancel = Arc::new(AtomicBool::new(false));
|
|
117
|
+
let worker_cancel = cancel.clone();
|
|
112
118
|
let (tx, rx) = mpsc::channel();
|
|
113
|
-
let worker = std::thread::spawn(move ||
|
|
119
|
+
let worker = std::thread::spawn(move || {
|
|
120
|
+
run_parallel_search(root, opts, filters, matcher, tx, worker_cancel)
|
|
121
|
+
});
|
|
114
122
|
Ok(RgIter {
|
|
115
123
|
rx,
|
|
124
|
+
cancel,
|
|
116
125
|
_worker: worker,
|
|
117
126
|
})
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
pub struct RgIter {
|
|
121
130
|
rx: Receiver<Result<SearchLine, RgApiError>>,
|
|
131
|
+
cancel: Arc<AtomicBool>,
|
|
122
132
|
_worker: std::thread::JoinHandle<()>,
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
impl RgIter {
|
|
136
|
+
pub fn cancel(&self) {
|
|
137
|
+
self.cancel.store(true, Ordering::Relaxed);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pub fn next_timeout(
|
|
141
|
+
&mut self,
|
|
142
|
+
timeout: Duration,
|
|
143
|
+
) -> Result<Result<SearchLine, RgApiError>, RecvTimeoutError> {
|
|
144
|
+
self.rx.recv_timeout(timeout)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
125
148
|
impl Iterator for RgIter {
|
|
126
149
|
type Item = Result<SearchLine, RgApiError>;
|
|
127
150
|
fn next(&mut self) -> Option<Self::Item> {
|
|
128
151
|
self.rx.recv().ok()
|
|
129
152
|
}
|
|
130
153
|
}
|
|
154
|
+
impl Drop for RgIter {
|
|
155
|
+
fn drop(&mut self) {
|
|
156
|
+
self.cancel();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
131
159
|
|
|
132
160
|
fn run_parallel_search(
|
|
133
161
|
root: PathBuf,
|
|
@@ -135,6 +163,7 @@ fn run_parallel_search(
|
|
|
135
163
|
filters: Arc<PathFilters>,
|
|
136
164
|
matcher: RegexMatcher,
|
|
137
165
|
tx: Sender<Result<SearchLine, RgApiError>>,
|
|
166
|
+
cancel: Arc<AtomicBool>,
|
|
138
167
|
) {
|
|
139
168
|
let mut walker = WalkBuilder::new(&root);
|
|
140
169
|
configure_walker(
|
|
@@ -157,6 +186,7 @@ fn run_parallel_search(
|
|
|
157
186
|
let matcher = matcher.clone();
|
|
158
187
|
let before_context = before_context;
|
|
159
188
|
let after_context = after_context;
|
|
189
|
+
let cancel = cancel.clone();
|
|
160
190
|
Box::new(move |entry| {
|
|
161
191
|
search_entry(
|
|
162
192
|
entry,
|
|
@@ -166,6 +196,7 @@ fn run_parallel_search(
|
|
|
166
196
|
before_context,
|
|
167
197
|
after_context,
|
|
168
198
|
&tx,
|
|
199
|
+
&cancel,
|
|
169
200
|
)
|
|
170
201
|
})
|
|
171
202
|
});
|
|
@@ -179,7 +210,11 @@ fn search_entry(
|
|
|
179
210
|
before_context: usize,
|
|
180
211
|
after_context: usize,
|
|
181
212
|
tx: &Sender<Result<SearchLine, RgApiError>>,
|
|
213
|
+
cancel: &Arc<AtomicBool>,
|
|
182
214
|
) -> WalkState {
|
|
215
|
+
if is_cancelled(cancel) {
|
|
216
|
+
return WalkState::Quit;
|
|
217
|
+
}
|
|
183
218
|
let dent = match entry {
|
|
184
219
|
Ok(dent) => dent,
|
|
185
220
|
Err(err) => return send_search_error(tx, RgApiError::new(err.to_string())),
|
|
@@ -198,10 +233,20 @@ fn search_entry(
|
|
|
198
233
|
if !filters.path_allowed(&rel) {
|
|
199
234
|
return WalkState::Continue;
|
|
200
235
|
}
|
|
201
|
-
match
|
|
236
|
+
match search_path_cancelable(
|
|
237
|
+
path,
|
|
238
|
+
rel,
|
|
239
|
+
matcher.clone(),
|
|
240
|
+
before_context,
|
|
241
|
+
after_context,
|
|
242
|
+
Some(cancel.clone()),
|
|
243
|
+
) {
|
|
202
244
|
Ok(lines) => {
|
|
245
|
+
if is_cancelled(cancel) {
|
|
246
|
+
return WalkState::Quit;
|
|
247
|
+
}
|
|
203
248
|
for line in lines {
|
|
204
|
-
if tx.send(Ok(line)).is_err() {
|
|
249
|
+
if is_cancelled(cancel) || tx.send(Ok(line)).is_err() {
|
|
205
250
|
return WalkState::Quit;
|
|
206
251
|
}
|
|
207
252
|
}
|
|
@@ -211,6 +256,10 @@ fn search_entry(
|
|
|
211
256
|
}
|
|
212
257
|
}
|
|
213
258
|
|
|
259
|
+
fn is_cancelled(cancel: &Arc<AtomicBool>) -> bool {
|
|
260
|
+
cancel.load(Ordering::Relaxed)
|
|
261
|
+
}
|
|
262
|
+
|
|
214
263
|
fn send_search_error(tx: &Sender<Result<SearchLine, RgApiError>>, err: RgApiError) -> WalkState {
|
|
215
264
|
let _ = tx.send(Err(err));
|
|
216
265
|
WalkState::Quit
|
|
@@ -250,6 +299,24 @@ pub fn search_path(
|
|
|
250
299
|
matcher: RegexMatcher,
|
|
251
300
|
before_context: usize,
|
|
252
301
|
after_context: usize,
|
|
302
|
+
) -> Result<Vec<SearchLine>, RgApiError> {
|
|
303
|
+
search_path_cancelable(
|
|
304
|
+
path,
|
|
305
|
+
display_path,
|
|
306
|
+
matcher,
|
|
307
|
+
before_context,
|
|
308
|
+
after_context,
|
|
309
|
+
None,
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn search_path_cancelable(
|
|
314
|
+
path: &Path,
|
|
315
|
+
display_path: String,
|
|
316
|
+
matcher: RegexMatcher,
|
|
317
|
+
before_context: usize,
|
|
318
|
+
after_context: usize,
|
|
319
|
+
cancel: Option<Arc<AtomicBool>>,
|
|
253
320
|
) -> Result<Vec<SearchLine>, RgApiError> {
|
|
254
321
|
let mut builder = SearcherBuilder::new();
|
|
255
322
|
builder
|
|
@@ -264,6 +331,7 @@ pub fn search_path(
|
|
|
264
331
|
path: display_path,
|
|
265
332
|
matcher,
|
|
266
333
|
lines: &mut out,
|
|
334
|
+
cancel,
|
|
267
335
|
};
|
|
268
336
|
match searcher.search_path(search_matcher, path, sink) {
|
|
269
337
|
Ok(()) => Ok(out),
|
|
@@ -305,6 +373,7 @@ fn search_bytes(
|
|
|
305
373
|
path: display_path,
|
|
306
374
|
matcher,
|
|
307
375
|
lines: &mut out,
|
|
376
|
+
cancel: None,
|
|
308
377
|
};
|
|
309
378
|
searcher
|
|
310
379
|
.search_slice(search_matcher, bytes, sink)
|
|
@@ -316,6 +385,15 @@ struct CollectSink<'a> {
|
|
|
316
385
|
path: String,
|
|
317
386
|
matcher: RegexMatcher,
|
|
318
387
|
lines: &'a mut Vec<SearchLine>,
|
|
388
|
+
cancel: Option<Arc<AtomicBool>>,
|
|
389
|
+
}
|
|
390
|
+
impl CollectSink<'_> {
|
|
391
|
+
fn cancelled(&self) -> bool {
|
|
392
|
+
match &self.cancel {
|
|
393
|
+
Some(cancel) => is_cancelled(cancel),
|
|
394
|
+
None => false,
|
|
395
|
+
}
|
|
396
|
+
}
|
|
319
397
|
}
|
|
320
398
|
|
|
321
399
|
impl Sink for CollectSink<'_> {
|
|
@@ -326,6 +404,9 @@ impl Sink for CollectSink<'_> {
|
|
|
326
404
|
_searcher: &grep_searcher::Searcher,
|
|
327
405
|
mat: &SinkMatch<'_>,
|
|
328
406
|
) -> Result<bool, Self::Error> {
|
|
407
|
+
if self.cancelled() {
|
|
408
|
+
return Ok(false);
|
|
409
|
+
}
|
|
329
410
|
let line = bytes_to_line(mat.bytes())?;
|
|
330
411
|
let spans = spans_for(&self.matcher, mat.bytes())?;
|
|
331
412
|
self.lines.push(SearchLine {
|
|
@@ -335,7 +416,7 @@ impl Sink for CollectSink<'_> {
|
|
|
335
416
|
line,
|
|
336
417
|
matches: spans,
|
|
337
418
|
});
|
|
338
|
-
Ok(
|
|
419
|
+
Ok(!self.cancelled())
|
|
339
420
|
}
|
|
340
421
|
|
|
341
422
|
fn context(
|
|
@@ -343,6 +424,9 @@ impl Sink for CollectSink<'_> {
|
|
|
343
424
|
_searcher: &grep_searcher::Searcher,
|
|
344
425
|
ctx: &SinkContext<'_>,
|
|
345
426
|
) -> Result<bool, Self::Error> {
|
|
427
|
+
if self.cancelled() {
|
|
428
|
+
return Ok(false);
|
|
429
|
+
}
|
|
346
430
|
let kind = match ctx.kind() {
|
|
347
431
|
SinkContextKind::Before => SearchKind::Before,
|
|
348
432
|
SinkContextKind::After => SearchKind::After,
|
|
@@ -355,7 +439,7 @@ impl Sink for CollectSink<'_> {
|
|
|
355
439
|
line: bytes_to_line(ctx.bytes())?,
|
|
356
440
|
matches: Vec::new(),
|
|
357
441
|
});
|
|
358
|
-
Ok(
|
|
442
|
+
Ok(!self.cancelled())
|
|
359
443
|
}
|
|
360
444
|
fn binary_data(
|
|
361
445
|
&mut self,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import _thread, threading
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
1
5
|
from rgapi import Regex, SearchResults, compile, fd, rg, rg_iter, search_path, search_text, walk
|
|
2
6
|
|
|
3
7
|
|
|
@@ -31,6 +35,21 @@ def test_fd_is_relative_and_respects_ignore_hidden_and_globs(tmp_path):
|
|
|
31
35
|
assert set(fd(str(tmp_path), exclude="*.py")) == {"bad.txt", "bin.dat"}
|
|
32
36
|
assert set(walk(str(tmp_path), files=True, dirs=False)) == found
|
|
33
37
|
|
|
38
|
+
def test_pathlike_arguments_and_expanduser(tmp_path, monkeypatch):
|
|
39
|
+
make_tree(tmp_path)
|
|
40
|
+
assert "src/app.py" in fd(tmp_path)
|
|
41
|
+
assert walk(tmp_path, path_re=r"\.py$") == ["src/app.py"]
|
|
42
|
+
assert [r.path for r in rg("TODO", tmp_path, include="*.py")] == ["src/app.py"]
|
|
43
|
+
assert list(rg_iter("TODO", tmp_path, include="*.py")) == rg("TODO", tmp_path, include="*.py")
|
|
44
|
+
matcher = compile("TODO")
|
|
45
|
+
text_label = tmp_path / "memory.txt"
|
|
46
|
+
assert search_text(matcher, "TODO\n", path=text_label)[0].path == str(text_label)
|
|
47
|
+
display = tmp_path / "display.py"
|
|
48
|
+
assert search_path(matcher, tmp_path / "src" / "app.py", display_path=display)[0].path == str(display)
|
|
49
|
+
|
|
50
|
+
monkeypatch.setenv("HOME", str(tmp_path))
|
|
51
|
+
assert fd("~", glob="*.py") == ["src/app.py"]
|
|
52
|
+
|
|
34
53
|
|
|
35
54
|
def test_path_filters_prune_dirs_and_follow_links(tmp_path):
|
|
36
55
|
(tmp_path / "src").mkdir()
|
|
@@ -102,6 +121,15 @@ def test_rg_returns_structured_matches_context_and_relative_paths(tmp_path):
|
|
|
102
121
|
assert str(stream) == repr(stream)
|
|
103
122
|
|
|
104
123
|
|
|
124
|
+
def test_rg_keyboard_interrupt_cancels(tmp_path):
|
|
125
|
+
(tmp_path / "big.txt").write_text("alpha beta gamma\n" * 1_000_000)
|
|
126
|
+
timer = threading.Timer(0.001, _thread.interrupt_main)
|
|
127
|
+
try:
|
|
128
|
+
with pytest.raises(KeyboardInterrupt):
|
|
129
|
+
timer.start()
|
|
130
|
+
rg("needle_that_is_not_present", str(tmp_path))
|
|
131
|
+
finally: timer.cancel()
|
|
132
|
+
|
|
105
133
|
def test_direct_regex_and_search_apis(tmp_path):
|
|
106
134
|
make_tree(tmp_path)
|
|
107
135
|
matcher = compile("todo")
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
from . import _core
|
|
2
|
-
|
|
3
|
-
Regex = _core.Regex
|
|
4
|
-
SearchLine = _core.SearchLine
|
|
5
|
-
RgIter = _core.RgIter
|
|
6
|
-
compile = _core.compile
|
|
7
|
-
|
|
8
|
-
class SearchResults(list):
|
|
9
|
-
def __str__(self): return "\n".join(map(str, self))
|
|
10
|
-
def _repr_pretty_(self, p, cycle): p.text("..." if cycle else str(self))
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _listify(value):
|
|
14
|
-
if value is None: return []
|
|
15
|
-
if isinstance(value, str): return [value]
|
|
16
|
-
return list(value)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _filters(glob=None, include=None, exclude=None, ext=None):
|
|
20
|
-
includes = _listify(include) + _listify(glob)
|
|
21
|
-
for suffix in _listify(ext):
|
|
22
|
-
suffix = str(suffix)
|
|
23
|
-
if suffix.startswith("."): suffix = suffix[1:]
|
|
24
|
-
includes.append(f"*.{suffix}")
|
|
25
|
-
return includes, _listify(exclude)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _context(context, before_context, after_context):
|
|
29
|
-
if context: return context, context
|
|
30
|
-
return before_context, after_context
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def walk(root=".", hidden=False, ignore=True, max_depth=None, min_depth=None, max_filesize=None, follow_links=False,
|
|
34
|
-
same_file_system=False, path_re=None, skip_path_re=None, skip_dir=None, skip_dir_re=None, files=True, dirs=False):
|
|
35
|
-
return _core.walk(root, hidden, ignore, max_depth, min_depth, max_filesize, follow_links,
|
|
36
|
-
same_file_system, path_re, skip_path_re, _listify(skip_dir), skip_dir_re, files, dirs)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def fd(root=".", pattern=None, glob=None, include=None, exclude=None, ext=None, hidden=False, ignore=True, max_depth=None,
|
|
40
|
-
min_depth=None, max_filesize=None, follow_links=False, same_file_system=False, path_re=None,
|
|
41
|
-
skip_path_re=None, skip_dir=None, skip_dir_re=None, files=True, dirs=False):
|
|
42
|
-
include, exclude = _filters(glob, include, exclude, ext)
|
|
43
|
-
return _core.find(root, pattern, include, exclude, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
44
|
-
follow_links, same_file_system, path_re, skip_path_re, _listify(skip_dir), skip_dir_re, files, dirs)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
48
|
-
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
49
|
-
before_context, after_context, context):
|
|
50
|
-
include, exclude = _filters(glob, include, exclude, ext)
|
|
51
|
-
before_context, after_context = _context(context, before_context, after_context)
|
|
52
|
-
return (pattern, root, include, exclude, hidden, ignore, max_depth, min_depth, max_filesize, follow_links, same_file_system,
|
|
53
|
-
path_re, skip_path_re, _listify(skip_dir), skip_dir_re, case_sensitive, smart_case, before_context, after_context)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def rg(pattern, root=".", glob=None, include=None, exclude=None, ext=None, hidden=False, ignore=True, max_depth=None,
|
|
57
|
-
min_depth=None, max_filesize=None, follow_links=False, same_file_system=False, path_re=None,
|
|
58
|
-
skip_path_re=None, skip_dir=None, skip_dir_re=None, case_sensitive=None, smart_case=False, before_context=0, after_context=0,
|
|
59
|
-
context=0, paths=False, count=False):
|
|
60
|
-
assert not (paths and count), "paths and count are mutually exclusive"
|
|
61
|
-
args = _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
62
|
-
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
63
|
-
before_context, after_context, context)
|
|
64
|
-
if paths:
|
|
65
|
-
seen, res = set(), []
|
|
66
|
-
for row in _core.rg_iter(*args):
|
|
67
|
-
if row.kind != "match" or row.path in seen: continue
|
|
68
|
-
seen.add(row.path)
|
|
69
|
-
res.append(row.path)
|
|
70
|
-
return res
|
|
71
|
-
if count: return sum(len(row.matches) for row in _core.rg_iter(*args) if row.kind == "match")
|
|
72
|
-
return SearchResults(_core.rg(*args))
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def rg_iter(pattern, root=".", glob=None, include=None, exclude=None, ext=None, hidden=False, ignore=True, max_depth=None,
|
|
76
|
-
min_depth=None, max_filesize=None, follow_links=False, same_file_system=False, path_re=None, skip_path_re=None,
|
|
77
|
-
skip_dir=None, skip_dir_re=None, case_sensitive=None, smart_case=False, before_context=0, after_context=0, context=0):
|
|
78
|
-
args = _rg_args(pattern, root, glob, include, exclude, ext, hidden, ignore, max_depth, min_depth, max_filesize,
|
|
79
|
-
follow_links, same_file_system, path_re, skip_path_re, skip_dir, skip_dir_re, case_sensitive, smart_case,
|
|
80
|
-
before_context, after_context, context)
|
|
81
|
-
return _core.rg_iter(*args)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def search_text(matcher, text, path="<text>", before_context=0, after_context=0, context=0):
|
|
85
|
-
before_context, after_context = _context(context, before_context, after_context)
|
|
86
|
-
return SearchResults(_core.search_text(matcher, text, path, before_context, after_context))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def search_path(matcher, path, display_path=None, before_context=0, after_context=0, context=0):
|
|
90
|
-
before_context, after_context = _context(context, before_context, after_context)
|
|
91
|
-
return SearchResults(_core.search_path(matcher, path, display_path, before_context, after_context))
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
__all__ = [
|
|
95
|
-
"Regex",
|
|
96
|
-
"RgIter",
|
|
97
|
-
"SearchLine",
|
|
98
|
-
"SearchResults",
|
|
99
|
-
"compile",
|
|
100
|
-
"fd",
|
|
101
|
-
"rg",
|
|
102
|
-
"rg_iter",
|
|
103
|
-
"search_path",
|
|
104
|
-
"search_text",
|
|
105
|
-
"walk",
|
|
106
|
-
]
|
rgapi-0.1.0/tools/build.sh
DELETED
rgapi-0.1.0/tools/bump.sh
DELETED
rgapi-0.1.0/tools/bump2.sh
DELETED
rgapi-0.1.0/tools/release.sh
DELETED
rgapi-0.1.0/tools/test.sh
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|