Skip to content

Commit f21664c

Browse files
committed
path: add path.glob
1 parent 284e6ac commit f21664c

File tree

8 files changed

+266
-0
lines changed

8 files changed

+266
-0
lines changed

lib/path.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const {
3131
StringPrototypeToLowerCase,
3232
} = primordials;
3333

34+
const { glob: _glob } = internalBinding('path');
35+
3436
const {
3537
CHAR_UPPERCASE_A,
3638
CHAR_LOWERCASE_A,
@@ -153,6 +155,12 @@ function _format(sep, pathObject) {
153155
return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
154156
}
155157

158+
function glob(pattern, name) {
159+
validateString(pattern, 'pattern');
160+
validateString(name, 'name');
161+
return _glob(pattern, name);
162+
}
163+
156164
const win32 = {
157165
/**
158166
* path.resolve([from ...], to)
@@ -1064,6 +1072,7 @@ const win32 = {
10641072

10651073
return ret;
10661074
},
1075+
glob,
10671076

10681077
sep: '\\',
10691078
delimiter: ';',
@@ -1530,6 +1539,7 @@ const posix = {
15301539

15311540
return ret;
15321541
},
1542+
glob,
15331543

15341544
sep: '/',
15351545
delimiter: ':',

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
'src/node_metadata.cc',
107107
'src/node_options.cc',
108108
'src/node_os.cc',
109+
'src/node_path.cc',
109110
'src/node_perf.cc',
110111
'src/node_platform.cc',
111112
'src/node_postmortem_metadata.cc',
@@ -225,6 +226,7 @@
225226
'src/node_object_wrap.h',
226227
'src/node_options.h',
227228
'src/node_options-inl.h',
229+
'src/node_path.h',
228230
'src/node_perf.h',
229231
'src/node_perf_common.h',
230232
'src/node_platform.h',

src/node_binding.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
V(mksnapshot) \
5353
V(options) \
5454
V(os) \
55+
V(path) \
5556
V(performance) \
5657
V(permission) \
5758
V(pipe_wrap) \

src/node_external_reference.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ namespace node {
1313
using CFunctionCallbackWithOneByteString =
1414
uint32_t (*)(v8::Local<v8::Value>, const v8::FastOneByteString&);
1515
using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);
16+
using CFunctionCallbackWithTwoOneByteStringsReturningBool =
17+
bool (*)(v8::Local<v8::Value>,
18+
const v8::FastOneByteString&,
19+
const v8::FastOneByteString&);
20+
using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);
1621
using CFunctionCallbackReturnDouble =
1722
double (*)(v8::Local<v8::Object> receiver);
1823
using CFunctionCallbackWithInt64 = void (*)(v8::Local<v8::Object> receiver,
@@ -29,6 +34,7 @@ class ExternalReferenceRegistry {
2934
#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
3035
V(CFunctionCallback) \
3136
V(CFunctionCallbackWithOneByteString) \
37+
V(CFunctionCallbackWithTwoOneByteStringsReturningBool) \
3238
V(CFunctionCallbackReturnDouble) \
3339
V(CFunctionCallbackWithInt64) \
3440
V(CFunctionCallbackWithBool) \
@@ -90,6 +96,7 @@ class ExternalReferenceRegistry {
9096
V(module_wrap) \
9197
V(options) \
9298
V(os) \
99+
V(path) \
93100
V(performance) \
94101
V(permission) \
95102
V(process_methods) \

src/node_path.cc

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#include "node_path.h"
2+
#include "env-inl.h"
3+
#include "node_errors.h"
4+
#include "node_external_reference.h"
5+
#include "util-inl.h"
6+
#include "v8-fast-api-calls.h"
7+
8+
namespace node {
9+
10+
namespace path {
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Local;
14+
using v8::Object;
15+
using v8::Value;
16+
17+
// extracted from
18+
// https://github.com/torvalds/linux/blob/cdc9718d5e590d6905361800b938b93f2b66818e/lib/glob.c
19+
bool glob(char const* pat, char const* str) {
20+
/*
21+
* Backtrack to previous * on mismatch and retry starting one
22+
* character later in the string. Because * matches all characters
23+
* (no exception for /), it can be easily proved that there's
24+
* never a need to backtrack multiple levels.
25+
*/
26+
char const* back_pat = nullptr;
27+
char const* back_str = nullptr;
28+
29+
/*
30+
* Loop over each token (character or class) in pat, matching
31+
* it against the remaining unmatched tail of str. Return false
32+
* on mismatch, or true after matching the trailing nul bytes.
33+
*/
34+
for (;;) {
35+
unsigned char c = *str++;
36+
unsigned char d = *pat++;
37+
38+
switch (d) {
39+
case '?': /* Wildcard: anything but nul */
40+
if (c == '\0') return false;
41+
break;
42+
case '*': /* Any-length wildcard */
43+
if (*pat == '\0') /* Optimize trailing * case */
44+
return true;
45+
back_pat = pat;
46+
back_str = --str; /* Allow zero-length match */
47+
break;
48+
case '[': { /* Character class */
49+
bool match = false, inverted = (*pat == '!');
50+
char const* cls = pat + inverted;
51+
unsigned char a = *cls++;
52+
53+
/*
54+
* Iterate over each span in the character class.
55+
* A span is either a single character a, or a
56+
* range a-b. The first span may begin with ']'.
57+
*/
58+
do {
59+
unsigned char b = a;
60+
61+
if (a == '\0') /* Malformed */
62+
goto literal;
63+
64+
if (cls[0] == '-' && cls[1] != ']') {
65+
b = cls[1];
66+
67+
if (b == '\0') goto literal;
68+
69+
cls += 2;
70+
/* Any special action if a > b? */
71+
}
72+
match |= (a <= c && c <= b);
73+
} while ((a = *cls++) != ']');
74+
75+
if (match == inverted) goto backtrack;
76+
pat = cls;
77+
} break;
78+
case '\\':
79+
d = *pat++;
80+
[[fallthrough]];
81+
default: /* Literal character */
82+
literal:
83+
if (c == d) {
84+
if (d == '\0') return true;
85+
break;
86+
}
87+
backtrack:
88+
if (c == '\0' || !back_pat) return false; /* No point continuing */
89+
/* Try again from last *, one character later in str. */
90+
pat = back_pat;
91+
str = ++back_str;
92+
break;
93+
}
94+
}
95+
}
96+
97+
void SlowGlob(const FunctionCallbackInfo<Value>& args) {
98+
Environment* env = Environment::GetCurrent(args);
99+
CHECK_GE(args.Length(), 2);
100+
CHECK(args[0]->IsString());
101+
CHECK(args[1]->IsString());
102+
103+
std::string pattern = Utf8Value(env->isolate(), args[0]).ToString();
104+
std::string str = Utf8Value(env->isolate(), args[1]).ToString();
105+
args.GetReturnValue().Set(glob(pattern.c_str(), str.c_str()));
106+
}
107+
bool FastGlob(Local<Value> receiver,
108+
const v8::FastOneByteString& pattern,
109+
const v8::FastOneByteString& str) {
110+
return glob(pattern.data, str.data);
111+
}
112+
113+
v8::CFunction fast_glob_(v8::CFunction::Make(FastGlob));
114+
115+
void Initialize(Local<Object> target,
116+
Local<Value> unused,
117+
Local<Context> context,
118+
void* priv) {
119+
SetFastMethod(context, target, "glob", SlowGlob, &fast_glob_);
120+
}
121+
122+
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
123+
registry->Register(SlowGlob);
124+
registry->Register(FastGlob);
125+
registry->Register(fast_glob_.GetTypeInfo());
126+
}
127+
} // namespace path
128+
129+
} // namespace node
130+
131+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(path, node::path::Initialize)
132+
NODE_BINDING_EXTERNAL_REFERENCE(path, node::path::RegisterExternalReferences)

src/node_path.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef SRC_NODE_PATH_H_
2+
#define SRC_NODE_PATH_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "base_object.h"
7+
#include "node_snapshotable.h"
8+
#include "v8.h"
9+
10+
namespace node {
11+
12+
namespace path {} // namespace path
13+
} // namespace node
14+
15+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
16+
17+
#endif // SRC_NODE_PATH_H_

test/parallel/test-bootstrap-modules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const expectedModules = new Set([
4747
'NativeModule internal/process/task_queues',
4848
'NativeModule timers',
4949
'Internal Binding trace_events',
50+
'Internal Binding path',
5051
'NativeModule internal/constants',
5152
'NativeModule path',
5253
'NativeModule internal/process/execution',

test/parallel/test-path-glob.mjs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import '../common/index.mjs';
2+
import { describe, it } from 'node:test';
3+
import * as assert from 'node:assert';
4+
import * as path from 'node:path';
5+
6+
7+
// https://github.com/torvalds/linux/blob/cdc9718d5e590d6905361800b938b93f2b66818e/lib/globtest.c
8+
const patterns = [
9+
{ expected: true, pattern: 'a', name: 'a' },
10+
{ expected: false, pattern: 'a', name: 'b' },
11+
{ expected: false, pattern: 'a', name: 'aa' },
12+
{ expected: false, pattern: 'a', name: '' },
13+
{ expected: true, pattern: '', name: '' },
14+
{ expected: false, pattern: '', name: 'a' },
15+
/* Simple character class tests */
16+
{ expected: true, pattern: '[a]', name: 'a' },
17+
{ expected: false, pattern: '[a]', name: 'b' },
18+
{ expected: false, pattern: '[!a]', name: 'a' },
19+
{ expected: true, pattern: '[!a]', name: 'b' },
20+
{ expected: true, pattern: '[ab]', name: 'a' },
21+
{ expected: true, pattern: '[ab]', name: 'b' },
22+
{ expected: false, pattern: '[ab]', name: 'c' },
23+
{ expected: true, pattern: '[!ab]', name: 'c' },
24+
{ expected: true, pattern: '[a-c]', name: 'b' },
25+
{ expected: false, pattern: '[a-c]', name: 'd' },
26+
/* Corner cases in character class parsing */
27+
{ expected: true, pattern: '[a-c-e-g]', name: '-' },
28+
{ expected: false, pattern: '[a-c-e-g]', name: 'd' },
29+
{ expected: true, pattern: '[a-c-e-g]', name: 'f' },
30+
{ expected: true, pattern: '[]a-ceg-ik[]', name: 'a' },
31+
{ expected: true, pattern: '[]a-ceg-ik[]', name: ']' },
32+
{ expected: true, pattern: '[]a-ceg-ik[]', name: '[' },
33+
{ expected: true, pattern: '[]a-ceg-ik[]', name: 'h' },
34+
{ expected: false, pattern: '[]a-ceg-ik[]', name: 'f' },
35+
{ expected: false, pattern: '[!]a-ceg-ik[]', name: 'h' },
36+
{ expected: false, pattern: '[!]a-ceg-ik[]', name: ']' },
37+
{ expected: true, pattern: '[!]a-ceg-ik[]', name: 'f' },
38+
/* Simple wild cards */
39+
{ expected: true, pattern: '?', name: 'a' },
40+
{ expected: false, pattern: '?', name: 'aa' },
41+
{ expected: false, pattern: '??', name: 'a' },
42+
{ expected: true, pattern: '?x?', name: 'axb' },
43+
{ expected: false, pattern: '?x?', name: 'abx' },
44+
{ expected: false, pattern: '?x?', name: 'xab' },
45+
/* Asterisk wild cards (backtracking) */
46+
{ expected: false, pattern: '*??', name: 'a' },
47+
{ expected: true, pattern: '*??', name: 'ab' },
48+
{ expected: true, pattern: '*??', name: 'abc' },
49+
{ expected: true, pattern: '*??', name: 'abcd' },
50+
{ expected: false, pattern: '??*', name: 'a' },
51+
{ expected: true, pattern: '??*', name: 'ab' },
52+
{ expected: true, pattern: '??*', name: 'abc' },
53+
{ expected: true, pattern: '??*', name: 'abcd' },
54+
{ expected: false, pattern: '?*?', name: 'a' },
55+
{ expected: true, pattern: '?*?', name: 'ab' },
56+
{ expected: true, pattern: '?*?', name: 'abc' },
57+
{ expected: true, pattern: '?*?', name: 'abcd' },
58+
{ expected: true, pattern: '*b', name: 'b' },
59+
{ expected: true, pattern: '*b', name: 'ab' },
60+
{ expected: false, pattern: '*b', name: 'ba' },
61+
{ expected: true, pattern: '*b', name: 'bb' },
62+
{ expected: true, pattern: '*b', name: 'abb' },
63+
{ expected: true, pattern: '*b', name: 'bab' },
64+
{ expected: true, pattern: '*bc', name: 'abbc' },
65+
{ expected: true, pattern: '*bc', name: 'bc' },
66+
{ expected: true, pattern: '*bc', name: 'bbc' },
67+
{ expected: true, pattern: '*bc', name: 'bcbc' },
68+
/* Multiple asterisks (complex backtracking) */
69+
{ expected: true, pattern: '*ac*', name: 'abacadaeafag' },
70+
{ expected: true, pattern: '*ac*ae*ag*', name: 'abacadaeafag' },
71+
{ expected: true, pattern: '*a*b*[bc]*[ef]*g*', name: 'abacadaeafag' },
72+
{ expected: false, pattern: '*a*b*[ef]*[cd]*g*', name: 'abacadaeafag' },
73+
{ expected: true, pattern: '*abcd*', name: 'abcabcabcabcdefg' },
74+
{ expected: true, pattern: '*ab*cd*', name: 'abcabcabcabcdefg' },
75+
{ expected: true, pattern: '*abcd*abcdef*', name: 'abcabcdabcdeabcdefg' },
76+
{ expected: false, pattern: '*abcd*', name: 'abcabcabcabcefg' },
77+
{ expected: false, pattern: '*ab*cd*', name: 'abcabcabcabcefg' },
78+
];
79+
80+
const invalid = [null, undefined, 1, Number.MAX_SAFE_INTEGER, true, false, Symbol(), {}, [], () => {}];
81+
82+
describe('path.glob', () => {
83+
for (const { expected, pattern, name } of patterns) {
84+
it(`pattern "${pattern}" should ${expected ? '' : 'not '}match "${name}"`, () => {
85+
assert.strictEqual(path.glob(pattern, name), expected);
86+
});
87+
}
88+
89+
for (const x of invalid) {
90+
const name = typeof x === 'symbol' ? 'Symnol()' : x;
91+
it(`${name} should throw as a parameter`, () => {
92+
assert.throws(() => path.glob(x, ''), { code: 'ERR_INVALID_ARG_TYPE' });
93+
assert.throws(() => path.glob('', x), { code: 'ERR_INVALID_ARG_TYPE' });
94+
});
95+
}
96+
});

0 commit comments

Comments
 (0)