Skip to content

Commit 086cd5b

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

File tree

9 files changed

+284
-0
lines changed

9 files changed

+284
-0
lines changed

LICENSE

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,3 +2162,37 @@ The externally maintained libraries used by Node.js are:
21622162
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21632163
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21642164
"""
2165+
2166+
- glob(7), located at src/node_path,cc, is licensed as follows:
2167+
"""
2168+
Valid-License-Identifier: MIT
2169+
SPDX-URL: https://spdx.org/licenses/MIT.html
2170+
Usage-Guide:
2171+
To use the MIT License put the following SPDX tag/value pair into a
2172+
comment according to the placement guidelines in the licensing rules
2173+
documentation:
2174+
SPDX-License-Identifier: MIT
2175+
License-Text:
2176+
2177+
MIT License
2178+
2179+
Copyright (c) <year> <copyright holders>
2180+
2181+
Permission is hereby granted, free of charge, to any person obtaining a
2182+
copy of this software and associated documentation files (the "Software"),
2183+
to deal in the Software without restriction, including without limitation
2184+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
2185+
and/or sell copies of the Software, and to permit persons to whom the
2186+
Software is furnished to do so, subject to the following conditions:
2187+
2188+
The above copyright notice and this permission notice shall be included in
2189+
all copies or substantial portions of the Software.
2190+
2191+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2192+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2193+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2194+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2195+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
2196+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2197+
DEALINGS IN THE SOFTWARE.
2198+
"""

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: 1 addition & 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',

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

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+
});

tools/license-builder.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,7 @@ addlicense "node-fs-extra" "lib/internal/fs/cp" "$licenseText"
146146

147147
addlicense "base64" "deps/base64/base64/" "$(cat "${rootdir}/deps/base64/base64/LICENSE" || true)"
148148

149+
licenseText="$(curl -sL https://raw.githubusercontent.com/torvalds/linux/09a9639e56c01c7a00d6c0ca63f4c7c41abe075d/LICENSES/preferred/MIT)"
150+
addlicense "glob(7)" "src/node_path,cc" "$licenseText"
151+
149152
mv "$tmplicense" "$licensefile"

0 commit comments

Comments
 (0)