bare-ripgrep 0.0.0 → 0.0.1
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.
- package/CMakeLists.txt +51 -0
- package/Cargo.lock +375 -0
- package/Cargo.toml +25 -0
- package/LICENSE.md +198 -0
- package/NOTICE.md +19 -0
- package/README.md +95 -0
- package/binding.c +169 -0
- package/binding.js +3 -0
- package/index.d.ts +70 -0
- package/index.js +100 -0
- package/package.json +68 -1
- package/prebuilds/android-arm64/bare-ripgrep.bare +0 -0
- package/prebuilds/android-arm64/bare-ripgrep.node +0 -0
- package/prebuilds/android-x64/bare-ripgrep.bare +0 -0
- package/prebuilds/android-x64/bare-ripgrep.node +0 -0
- package/prebuilds/darwin-arm64/bare-ripgrep.bare +0 -0
- package/prebuilds/darwin-arm64/bare-ripgrep.node +0 -0
- package/prebuilds/darwin-x64/bare-ripgrep.bare +0 -0
- package/prebuilds/darwin-x64/bare-ripgrep.node +0 -0
- package/prebuilds/ios-arm64/bare-ripgrep.bare +0 -0
- package/prebuilds/ios-arm64-simulator/bare-ripgrep.bare +0 -0
- package/prebuilds/ios-x64-simulator/bare-ripgrep.bare +0 -0
- package/prebuilds/linux-arm64/bare-ripgrep.bare +0 -0
- package/prebuilds/linux-arm64/bare-ripgrep.node +0 -0
- package/prebuilds/linux-x64/bare-ripgrep.bare +0 -0
- package/prebuilds/linux-x64/bare-ripgrep.node +0 -0
- package/prebuilds/win32-arm64/bare-ripgrep.bare +0 -0
- package/prebuilds/win32-arm64/bare-ripgrep.node +0 -0
- package/prebuilds/win32-x64/bare-ripgrep.bare +0 -0
- package/prebuilds/win32-x64/bare-ripgrep.node +0 -0
- package/src/bare_ripgrep.h +23 -0
- package/src/glob.rs +47 -0
- package/src/lib.rs +111 -0
- package/src/request.rs +31 -0
- package/src/scope.rs +170 -0
- package/src/search.rs +224 -0
- package/src/walk.rs +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# bare-ripgrep
|
|
2
|
+
|
|
3
|
+
ripgrep's search and walk crates (`ignore`, `grep-searcher`, `grep-regex`) as a native addon for Bare and Node. It searches a local folder without an `rg` subprocess, so it also runs on iOS and Android.
|
|
4
|
+
|
|
5
|
+
The result is meant to match the `grep` tool of qvac-harness (`lib/tools/fs/search.ts`, `walk.ts`, `ignore.ts`, `glob.ts`) while being faster and not stopping at its 10k-entry JS walk cap.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm i bare-ripgrep
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const { search, walk } = require('bare-ripgrep')
|
|
17
|
+
|
|
18
|
+
const { hits, truncated, cut } = await search({
|
|
19
|
+
root: '/path/to/project',
|
|
20
|
+
queries: ['todo', 'fixme'],
|
|
21
|
+
include: '*.ts'
|
|
22
|
+
})
|
|
23
|
+
// hits: [{ path: 'src/a.ts', line: 12, text: '// TODO: ...' }]
|
|
24
|
+
|
|
25
|
+
const { files } = await walk({ root: '/path/to/project', folder: 'src' })
|
|
26
|
+
// files: [{ path: 'src/a.ts', mtime: 1760000000000 }]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `search(options)`
|
|
30
|
+
|
|
31
|
+
| Option | Default | Meaning |
|
|
32
|
+
| ---------------- | ------- | ----------------------------------------------------------------------------------------- |
|
|
33
|
+
| `root` | | Absolute directory. Returned paths are relative to it, `/`-separated. |
|
|
34
|
+
| `folder` | `''` | Subfolder of `root` to search. |
|
|
35
|
+
| `queries` | | Each query is an alternative. Literal text unless `regex`. |
|
|
36
|
+
| `regex` | `false` | Queries are regular expressions (Rust `regex` syntax). |
|
|
37
|
+
| `caseSensitive` | `false` | |
|
|
38
|
+
| `include` | | Glob or array of globs. A bare name matches the basename at any depth; with `/` the path. |
|
|
39
|
+
| `deny` | | Absolute paths whose files and folders are skipped, case-insensitively. |
|
|
40
|
+
| `includeIgnored` | `false` | Also walk what `.gitignore`, `.ignore` and the hidden tool folders leave out. |
|
|
41
|
+
| `maxMatches` | `1000` | Most hits returned. |
|
|
42
|
+
| `maxEntries` | `10000` | Most entries (files and folders) the walk keeps. |
|
|
43
|
+
| `signal` | | `AbortSignal`. Abort rejects with an `AbortError` and stops the native work. |
|
|
44
|
+
|
|
45
|
+
Resolves `{ hits: { path, line, text }[], truncated, cut }`. Hits are sorted by path, then line. `truncated` is set when there were more hits than `maxMatches` or more entries than `maxEntries`. `cut` is set when a line past 1 MiB was searched in its head only.
|
|
46
|
+
|
|
47
|
+
### `walk(options)`
|
|
48
|
+
|
|
49
|
+
Same scope options (`root`, `folder`, `include`, `deny`, `includeIgnored`, `maxEntries`, `signal`). Resolves `{ files: { path, mtime }[], truncated }`, sorted by path; `mtime` is in milliseconds.
|
|
50
|
+
|
|
51
|
+
## Semantics
|
|
52
|
+
|
|
53
|
+
- Case-insensitive by default. Queries are literal unless `regex`.
|
|
54
|
+
- A file with a NUL in its first 1024 bytes is binary and skipped; a later NUL is text.
|
|
55
|
+
- `.gitignore` and `.ignore` are honoured with or without a git repository, `.ignore` wins. Ignore files between `root` and `folder` apply. `.git/info/exclude`, global ignore files and ignore files above `root` are not read. Dotfiles are searched.
|
|
56
|
+
- `.git`, `node_modules` and `.trash` are always skipped. `.hg`, `.svn`, `.venv`, `.cache` and `.next` are skipped unless `includeIgnored`.
|
|
57
|
+
- Symlinks are never followed, files or folders.
|
|
58
|
+
- Hit text is the line without its `\n` (a `\r` stays), cut to 500 UTF-16 units. A line past 1 MiB is searched in its head only.
|
|
59
|
+
- With `maxMatches`, the hits kept are the first by path then line, whatever the thread timing.
|
|
60
|
+
|
|
61
|
+
## Differences from the harness engine
|
|
62
|
+
|
|
63
|
+
- **Regex dialect.** Queries with `regex` use Rust's `regex` syntax, not JavaScript's: no look-around and no backreferences (they reject with `invalid regex`), `\w`, `\d` and `\b` are Unicode-aware, a pattern naming a newline is rejected, and `.` also matches `\r`. Literal queries are unaffected.
|
|
64
|
+
- **Case folding.** `caseSensitive: false` uses Unicode simple case folding, which can differ from JS `i` on a few characters.
|
|
65
|
+
- **Ignore engine.** Ignore rules are git's, through the `ignore` crate, not the harness's regex translation; edge cases of exotic patterns can differ. A `folder` that an ancestor's ignore file ignores yields nothing; the harness searches it.
|
|
66
|
+
- **Caps under parallelism.** The walk is parallel. When `maxEntries` cuts it short, which entries were kept can differ between runs. The `maxMatches` result is deterministic. Entries under a `deny` path are pruned, not counted.
|
|
67
|
+
- **Long lines.** A line past 1 MiB is searched in its head only and sets `cut` (the harness sets `cut` only for a cut drive read). A line past 16 MiB ends that file's scan, keeps its earlier hits and sets `cut`.
|
|
68
|
+
- **Text clipping.** A surrogate pair split by the 500-unit cut is dropped whole; JS would keep a lone surrogate.
|
|
69
|
+
- **Not provided.** `maxMs` and `skippedLinks`. A file that cannot be opened is skipped silently.
|
|
70
|
+
|
|
71
|
+
## Build
|
|
72
|
+
|
|
73
|
+
`bare-make generate && bare-make build && bare-make install` (`npm run build`) writes `prebuilds/<platform>-<arch>/bare-ripgrep.bare` and `bare-ripgrep.node`: one build, one C binding (`binding.c`, written against `js.h`), loaded by Bare directly and by Node through `bare-compat-napi`.
|
|
74
|
+
|
|
75
|
+
The Rust crate (`Cargo.toml`, `src/`) builds as a `staticlib` through `cmake-cargo`, which runs `cargo build --release --target <triple>` and imports the archive. It needs `cargo` and the rustup target of the platform being built. The C side only passes a JSON request to Rust and a JSON result back; the work runs on the libuv thread pool (`uv_queue_work`), and the walk fans out over its own threads. The Rust entry points never unwind into C.
|
|
76
|
+
|
|
77
|
+
| Target | Rust triple |
|
|
78
|
+
| ------------------------------------------ | ------------------------------------------------------- |
|
|
79
|
+
| `darwin-arm64`, `darwin-x64` | `aarch64-apple-darwin`, `x86_64-apple-darwin` |
|
|
80
|
+
| `linux-arm64`, `linux-x64` | `aarch64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu` |
|
|
81
|
+
| `win32-arm64`, `win32-x64` | `aarch64-pc-windows-msvc`, `x86_64-pc-windows-msvc` |
|
|
82
|
+
| `ios-arm64` | `aarch64-apple-ios` |
|
|
83
|
+
| `ios-arm64-simulator`, `ios-x64-simulator` | `aarch64-apple-ios-sim`, `x86_64-apple-ios` |
|
|
84
|
+
| `android-arm64`, `android-x64` | `aarch64-linux-android`, `x86_64-linux-android` |
|
|
85
|
+
|
|
86
|
+
## Test and bench
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
npm test # brittle, on Node and on Bare
|
|
90
|
+
node bench/index.js <dir> [pattern...] # search() against `rg -i -F` under the same ignore rules
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Apache-2.0
|
package/binding.c
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#include <assert.h>
|
|
2
|
+
#include <bare.h>
|
|
3
|
+
#include <js.h>
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
#include <uv.h>
|
|
8
|
+
|
|
9
|
+
#include "src/bare_ripgrep.h"
|
|
10
|
+
|
|
11
|
+
typedef struct {
|
|
12
|
+
uv_work_t handle;
|
|
13
|
+
js_env_t *env;
|
|
14
|
+
js_ref_t *cb;
|
|
15
|
+
js_deferred_teardown_t *teardown;
|
|
16
|
+
rg_job_t *job;
|
|
17
|
+
bool exiting;
|
|
18
|
+
} bare_ripgrep_job_t;
|
|
19
|
+
|
|
20
|
+
static void
|
|
21
|
+
bare_ripgrep__on_work (uv_work_t *handle) {
|
|
22
|
+
bare_ripgrep_job_t *job = handle->data;
|
|
23
|
+
|
|
24
|
+
rg_job_run(job->job);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static void
|
|
28
|
+
bare_ripgrep__on_after_work (uv_work_t *handle, int status) {
|
|
29
|
+
int err;
|
|
30
|
+
|
|
31
|
+
bare_ripgrep_job_t *job = handle->data;
|
|
32
|
+
js_env_t *env = job->env;
|
|
33
|
+
js_deferred_teardown_t *teardown = job->teardown;
|
|
34
|
+
|
|
35
|
+
if (!job->exiting) {
|
|
36
|
+
js_handle_scope_t *scope;
|
|
37
|
+
err = js_open_handle_scope(env, &scope);
|
|
38
|
+
assert(err == 0);
|
|
39
|
+
|
|
40
|
+
js_value_t *cb;
|
|
41
|
+
err = js_get_reference_value(env, job->cb, &cb);
|
|
42
|
+
assert(err == 0);
|
|
43
|
+
|
|
44
|
+
size_t len;
|
|
45
|
+
const char *json = rg_job_result(job->job, &len);
|
|
46
|
+
|
|
47
|
+
js_value_t *argv[1];
|
|
48
|
+
err = js_create_string_utf8(env, (const utf8_t *) json, len, &argv[0]);
|
|
49
|
+
assert(err == 0);
|
|
50
|
+
|
|
51
|
+
js_value_t *global;
|
|
52
|
+
err = js_get_global(env, &global);
|
|
53
|
+
assert(err == 0);
|
|
54
|
+
|
|
55
|
+
js_call_function_with_checkpoint(env, global, cb, 1, argv, NULL);
|
|
56
|
+
|
|
57
|
+
err = js_close_handle_scope(env, scope);
|
|
58
|
+
assert(err == 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
err = js_delete_reference(env, job->cb);
|
|
62
|
+
assert(err == 0);
|
|
63
|
+
|
|
64
|
+
rg_job_free(job->job);
|
|
65
|
+
free(job);
|
|
66
|
+
|
|
67
|
+
err = js_finish_deferred_teardown_callback(teardown);
|
|
68
|
+
assert(err == 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static void
|
|
72
|
+
bare_ripgrep__on_teardown (js_deferred_teardown_t *handle, void *data) {
|
|
73
|
+
bare_ripgrep_job_t *job = data;
|
|
74
|
+
|
|
75
|
+
job->exiting = true;
|
|
76
|
+
|
|
77
|
+
rg_job_cancel(job->job);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static js_value_t *
|
|
81
|
+
bare_ripgrep_start (js_env_t *env, js_callback_info_t *info) {
|
|
82
|
+
int err;
|
|
83
|
+
|
|
84
|
+
size_t argc = 2;
|
|
85
|
+
js_value_t *argv[2];
|
|
86
|
+
|
|
87
|
+
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
|
|
88
|
+
assert(err == 0);
|
|
89
|
+
|
|
90
|
+
assert(argc == 2);
|
|
91
|
+
|
|
92
|
+
size_t len;
|
|
93
|
+
err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
|
|
94
|
+
assert(err == 0);
|
|
95
|
+
|
|
96
|
+
utf8_t *json = malloc(len + 1);
|
|
97
|
+
err = js_get_value_string_utf8(env, argv[0], json, len + 1, &len);
|
|
98
|
+
assert(err == 0);
|
|
99
|
+
|
|
100
|
+
bare_ripgrep_job_t *job = malloc(sizeof(bare_ripgrep_job_t));
|
|
101
|
+
job->env = env;
|
|
102
|
+
job->exiting = false;
|
|
103
|
+
job->handle.data = job;
|
|
104
|
+
job->job = rg_job_new((const char *) json, len);
|
|
105
|
+
|
|
106
|
+
free(json);
|
|
107
|
+
|
|
108
|
+
err = js_create_reference(env, argv[1], 1, &job->cb);
|
|
109
|
+
assert(err == 0);
|
|
110
|
+
|
|
111
|
+
uv_loop_t *loop;
|
|
112
|
+
err = js_get_env_loop(env, &loop);
|
|
113
|
+
assert(err == 0);
|
|
114
|
+
|
|
115
|
+
err = js_add_deferred_teardown_callback(env, bare_ripgrep__on_teardown, job, &job->teardown);
|
|
116
|
+
assert(err == 0);
|
|
117
|
+
|
|
118
|
+
err = uv_queue_work(loop, &job->handle, bare_ripgrep__on_work, bare_ripgrep__on_after_work);
|
|
119
|
+
assert(err == 0);
|
|
120
|
+
|
|
121
|
+
js_value_t *handle;
|
|
122
|
+
err = js_create_external(env, job, NULL, NULL, &handle);
|
|
123
|
+
assert(err == 0);
|
|
124
|
+
|
|
125
|
+
return handle;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static js_value_t *
|
|
129
|
+
bare_ripgrep_cancel (js_env_t *env, js_callback_info_t *info) {
|
|
130
|
+
int err;
|
|
131
|
+
|
|
132
|
+
size_t argc = 1;
|
|
133
|
+
js_value_t *argv[1];
|
|
134
|
+
|
|
135
|
+
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
|
|
136
|
+
assert(err == 0);
|
|
137
|
+
|
|
138
|
+
assert(argc == 1);
|
|
139
|
+
|
|
140
|
+
bare_ripgrep_job_t *job;
|
|
141
|
+
err = js_get_value_external(env, argv[0], (void **) &job);
|
|
142
|
+
assert(err == 0);
|
|
143
|
+
|
|
144
|
+
rg_job_cancel(job->job);
|
|
145
|
+
|
|
146
|
+
return NULL;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static js_value_t *
|
|
150
|
+
bare_ripgrep_exports (js_env_t *env, js_value_t *exports) {
|
|
151
|
+
int err;
|
|
152
|
+
|
|
153
|
+
#define V(name, fn) \
|
|
154
|
+
{ \
|
|
155
|
+
js_value_t *val; \
|
|
156
|
+
err = js_create_function(env, name, -1, fn, NULL, &val); \
|
|
157
|
+
assert(err == 0); \
|
|
158
|
+
err = js_set_named_property(env, exports, name, val); \
|
|
159
|
+
assert(err == 0); \
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
V("start", bare_ripgrep_start)
|
|
163
|
+
V("cancel", bare_ripgrep_cancel)
|
|
164
|
+
#undef V
|
|
165
|
+
|
|
166
|
+
return exports;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
BARE_MODULE(bare_ripgrep, bare_ripgrep_exports)
|
package/binding.js
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface Hit {
|
|
2
|
+
/** Relative to `root`, `/`-separated. */
|
|
3
|
+
path: string
|
|
4
|
+
/** 1-based. */
|
|
5
|
+
line: number
|
|
6
|
+
/** The line without its newline, at most 500 UTF-16 units. */
|
|
7
|
+
text: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface WalkedFile {
|
|
11
|
+
/** Relative to `root`, `/`-separated. */
|
|
12
|
+
path: string
|
|
13
|
+
/** Milliseconds since the epoch. */
|
|
14
|
+
mtime: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Scope {
|
|
18
|
+
/** Absolute directory every returned path is relative to. */
|
|
19
|
+
root: string
|
|
20
|
+
/** Subfolder of `root` to search, `''` for all of it. */
|
|
21
|
+
folder?: string
|
|
22
|
+
/**
|
|
23
|
+
* Glob a file must match: a bare name matches the basename at any depth, a pattern with
|
|
24
|
+
* `/` matches the path relative to `root`. Several globs are alternatives.
|
|
25
|
+
*/
|
|
26
|
+
include?: string | string[]
|
|
27
|
+
/** Absolute paths whose files and folders are skipped (case-insensitive). */
|
|
28
|
+
deny?: string[]
|
|
29
|
+
/** Also walk what ignore files and the hidden tool folders leave out. */
|
|
30
|
+
includeIgnored?: boolean
|
|
31
|
+
/** Most entries (files and folders) the walk keeps, default 10000. */
|
|
32
|
+
maxEntries?: number
|
|
33
|
+
signal?: AbortSignal
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SearchOptions extends Scope {
|
|
37
|
+
/** Each query is an alternative. */
|
|
38
|
+
queries: string[]
|
|
39
|
+
/** Queries are regular expressions rather than literals. */
|
|
40
|
+
regex?: boolean
|
|
41
|
+
caseSensitive?: boolean
|
|
42
|
+
/** Most hits returned, default 1000. */
|
|
43
|
+
maxMatches?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SearchResult {
|
|
47
|
+
/** Sorted by path, then line. */
|
|
48
|
+
hits: Hit[]
|
|
49
|
+
/** More hits or entries than the caps allowed. */
|
|
50
|
+
truncated: boolean
|
|
51
|
+
/** A line past 1 MiB was searched in its head only. */
|
|
52
|
+
cut: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface WalkOptions extends Scope {}
|
|
56
|
+
|
|
57
|
+
export interface WalkResult {
|
|
58
|
+
/** Sorted by path. */
|
|
59
|
+
files: WalkedFile[]
|
|
60
|
+
truncated: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class AbortError extends Error {
|
|
64
|
+
name: 'AbortError'
|
|
65
|
+
code: 'ABORT_ERR'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function search(options: SearchOptions): Promise<SearchResult>
|
|
69
|
+
|
|
70
|
+
export function walk(options: WalkOptions): Promise<WalkResult>
|
package/index.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const binding = require('./binding')
|
|
2
|
+
|
|
3
|
+
const MAX_MATCHES = 1000
|
|
4
|
+
const MAX_ENTRIES = 10_000
|
|
5
|
+
|
|
6
|
+
class AbortError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('The operation was aborted')
|
|
9
|
+
this.name = 'AbortError'
|
|
10
|
+
this.code = 'ABORT_ERR'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.AbortError = AbortError
|
|
15
|
+
|
|
16
|
+
exports.search = function search({
|
|
17
|
+
root,
|
|
18
|
+
folder = '',
|
|
19
|
+
queries,
|
|
20
|
+
regex = false,
|
|
21
|
+
caseSensitive = false,
|
|
22
|
+
include,
|
|
23
|
+
deny,
|
|
24
|
+
includeIgnored = false,
|
|
25
|
+
maxMatches = MAX_MATCHES,
|
|
26
|
+
maxEntries = MAX_ENTRIES,
|
|
27
|
+
signal
|
|
28
|
+
}) {
|
|
29
|
+
return run(
|
|
30
|
+
{
|
|
31
|
+
op: 'search',
|
|
32
|
+
root,
|
|
33
|
+
folder,
|
|
34
|
+
queries,
|
|
35
|
+
regex,
|
|
36
|
+
caseSensitive,
|
|
37
|
+
include: list(include),
|
|
38
|
+
deny: list(deny),
|
|
39
|
+
includeIgnored,
|
|
40
|
+
maxMatches: count(maxMatches),
|
|
41
|
+
maxEntries: count(maxEntries)
|
|
42
|
+
},
|
|
43
|
+
signal
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
exports.walk = function walk({
|
|
48
|
+
root,
|
|
49
|
+
folder = '',
|
|
50
|
+
include,
|
|
51
|
+
deny,
|
|
52
|
+
includeIgnored = false,
|
|
53
|
+
maxEntries = MAX_ENTRIES,
|
|
54
|
+
signal
|
|
55
|
+
}) {
|
|
56
|
+
return run(
|
|
57
|
+
{
|
|
58
|
+
op: 'walk',
|
|
59
|
+
root,
|
|
60
|
+
folder,
|
|
61
|
+
include: list(include),
|
|
62
|
+
deny: list(deny),
|
|
63
|
+
includeIgnored,
|
|
64
|
+
maxMatches: 0,
|
|
65
|
+
maxEntries: count(maxEntries)
|
|
66
|
+
},
|
|
67
|
+
signal
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function run(request, signal) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
if (signal && signal.aborted) return reject(new AbortError())
|
|
74
|
+
|
|
75
|
+
let job = null
|
|
76
|
+
|
|
77
|
+
const onabort = () => {
|
|
78
|
+
binding.cancel(job)
|
|
79
|
+
reject(new AbortError())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
job = binding.start(JSON.stringify(request), (json) => {
|
|
83
|
+
if (signal) signal.removeEventListener('abort', onabort)
|
|
84
|
+
|
|
85
|
+
const result = JSON.parse(json)
|
|
86
|
+
if (result.error) reject(new Error(result.error))
|
|
87
|
+
else resolve(result)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (signal) signal.addEventListener('abort', onabort, { once: true })
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function list(value) {
|
|
95
|
+
return value === undefined ? [] : [].concat(value)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function count(value) {
|
|
99
|
+
return Math.min(value, Number.MAX_SAFE_INTEGER)
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1,68 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "bare-ripgrep",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "ripgrep's search and walk crates as a native addon for Bare and Node",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"default": "./index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"package.json",
|
|
15
|
+
"index.js",
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"binding.js",
|
|
18
|
+
"binding.c",
|
|
19
|
+
"Cargo.toml",
|
|
20
|
+
"Cargo.lock",
|
|
21
|
+
"src",
|
|
22
|
+
"CMakeLists.txt",
|
|
23
|
+
"NOTICE.md",
|
|
24
|
+
"prebuilds"
|
|
25
|
+
],
|
|
26
|
+
"addon": true,
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bare-make generate && bare-make build && bare-make install",
|
|
29
|
+
"bench": "node bench/index.js",
|
|
30
|
+
"format": "prettier . --write",
|
|
31
|
+
"test": "npm run test:node && npm run test:bare",
|
|
32
|
+
"test:node": "brittle-node test/all.mjs",
|
|
33
|
+
"test:bare": "brittle-bare test/all.mjs",
|
|
34
|
+
"test:generate": "brittle-make-test test/all.mjs test/*.test.js",
|
|
35
|
+
"lint": "prettier . --check && lunte"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/tetherto/bare-ripgrep.git"
|
|
40
|
+
},
|
|
41
|
+
"author": "Tether Inc",
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/tetherto/bare-ripgrep/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/tetherto/bare-ripgrep",
|
|
47
|
+
"engines": {
|
|
48
|
+
"bare": ">=1.16.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"require-addon": "^1.1.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"bare-abort-controller": "^1.0.0",
|
|
55
|
+
"bare-compat-napi": "^1.3.5",
|
|
56
|
+
"bare-fs": "^4.1.2",
|
|
57
|
+
"bare-make": "^1.7.0",
|
|
58
|
+
"bare-os": "^3.9.4",
|
|
59
|
+
"bare-path": "^3.0.0",
|
|
60
|
+
"brittle": "^4.1.0",
|
|
61
|
+
"cmake-bare": "^1.8.0",
|
|
62
|
+
"cmake-cargo": "^0.1.1",
|
|
63
|
+
"cmake-napi": "^1.2.1",
|
|
64
|
+
"lunte": "^1.8.4",
|
|
65
|
+
"prettier": "^3.9.6",
|
|
66
|
+
"prettier-config-holepunch": "^2.0.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#ifndef BARE_RIPGREP_H
|
|
2
|
+
#define BARE_RIPGREP_H
|
|
3
|
+
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
|
|
6
|
+
typedef struct rg_job_s rg_job_t;
|
|
7
|
+
|
|
8
|
+
rg_job_t *
|
|
9
|
+
rg_job_new (const char *json, size_t len);
|
|
10
|
+
|
|
11
|
+
void
|
|
12
|
+
rg_job_run (const rg_job_t *job);
|
|
13
|
+
|
|
14
|
+
void
|
|
15
|
+
rg_job_cancel (const rg_job_t *job);
|
|
16
|
+
|
|
17
|
+
const char *
|
|
18
|
+
rg_job_result (const rg_job_t *job, size_t *len);
|
|
19
|
+
|
|
20
|
+
void
|
|
21
|
+
rg_job_free (rg_job_t *job);
|
|
22
|
+
|
|
23
|
+
#endif
|
package/src/glob.rs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use regex::Regex;
|
|
2
|
+
|
|
3
|
+
/// Mirrors qvac-harness `compileGlob`: `*` and `?` stay inside a segment, `**/` spans
|
|
4
|
+
/// directories, anything else is literal. A pattern with `/` tests the path, else the name.
|
|
5
|
+
pub struct Glob {
|
|
6
|
+
regex: Regex,
|
|
7
|
+
on_path: bool,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
impl Glob {
|
|
11
|
+
pub fn new(pattern: &str) -> Result<Glob, String> {
|
|
12
|
+
let pattern = pattern.strip_prefix("./").unwrap_or(pattern);
|
|
13
|
+
let chars: Vec<char> = pattern.chars().collect();
|
|
14
|
+
let mut source = String::from("(?s)^");
|
|
15
|
+
let mut i = 0;
|
|
16
|
+
while i < chars.len() {
|
|
17
|
+
let rest = &chars[i..];
|
|
18
|
+
if rest.starts_with(&['*', '*', '/']) {
|
|
19
|
+
source.push_str("(?:.*/)?");
|
|
20
|
+
i += 3;
|
|
21
|
+
} else if rest.starts_with(&['*', '*']) {
|
|
22
|
+
source.push_str(".*");
|
|
23
|
+
i += 2;
|
|
24
|
+
} else if rest[0] == '*' {
|
|
25
|
+
source.push_str("[^/]*");
|
|
26
|
+
i += 1;
|
|
27
|
+
} else if rest[0] == '?' {
|
|
28
|
+
source.push_str("[^/]");
|
|
29
|
+
i += 1;
|
|
30
|
+
} else {
|
|
31
|
+
source.push_str(®ex::escape(&rest[0].to_string()));
|
|
32
|
+
i += 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
source.push('$');
|
|
36
|
+
|
|
37
|
+
let regex = Regex::new(&source).map_err(|err| format!("invalid include glob: {err}"))?;
|
|
38
|
+
Ok(Glob {
|
|
39
|
+
regex,
|
|
40
|
+
on_path: pattern.contains('/'),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn matches(&self, name: &str, path: &str) -> bool {
|
|
45
|
+
self.regex.is_match(if self.on_path { path } else { name })
|
|
46
|
+
}
|
|
47
|
+
}
|