Skip to content

Commit 8184e2a

Browse files
committed
path: add glob method
1 parent 951af83 commit 8184e2a

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

doc/api/path.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,27 @@ path.format({
279279
// Returns: 'C:\\path\\dir\\file.txt'
280280
```
281281

282+
## `path.glob(path, pattern)`
283+
284+
<!-- YAML
285+
added: REPLACEME
286+
-->
287+
288+
* `path` {string} The path to glob-match against.
289+
* `pattern` {string} The glob to check the path against.
290+
* Returns: {boolean} Whether or not the `path` matched the `pattern`.
291+
292+
The `path.glob()` method determines if `path` matches the `pattern`.
293+
294+
For example:
295+
296+
```js
297+
path.glob('/foo/bar', '/foo/*'); // true
298+
path.glob('/foo/bar*', 'foo/bird'); // false
299+
```
300+
301+
A [`TypeError`][] is thrown if `path` or `pattern` are not strings.
302+
282303
## `path.isAbsolute(path)`
283304

284305
<!-- YAML

lib/path.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,14 @@ const {
4747
validateString,
4848
} = require('internal/validators');
4949

50+
const {
51+
getLazy,
52+
} = require('internal/util');
53+
54+
const lazyMinimatch = getLazy(() => require('internal/deps/minimatch/index'));
55+
5056
const platformIsWin32 = (process.platform === 'win32');
57+
const platformIsOSX = (process.platform === 'darwin');
5158

5259
function isPathSeparator(code) {
5360
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
@@ -153,6 +160,21 @@ function _format(sep, pathObject) {
153160
return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
154161
}
155162

163+
function glob(path, pattern, windows) {
164+
validateString(path, 'path');
165+
validateString(pattern, 'pattern');
166+
return lazyMinimatch().minimatch(path, pattern, {
167+
__proto__: null,
168+
nocase: platformIsOSX || platformIsWin32,
169+
windowsPathsNoEscape: true,
170+
nonegate: true,
171+
nocomment: true,
172+
optimizationLevel: 2,
173+
platform: windows ? 'win32' : 'posix',
174+
nocaseMagicOnly: true,
175+
});
176+
}
177+
156178
const win32 = {
157179
/**
158180
* path.resolve([from ...], to)
@@ -1065,6 +1087,10 @@ const win32 = {
10651087
return ret;
10661088
},
10671089

1090+
glob(path, pattern) {
1091+
return glob(path, pattern, true);
1092+
},
1093+
10681094
sep: '\\',
10691095
delimiter: ';',
10701096
win32: null,
@@ -1530,6 +1556,10 @@ const posix = {
15301556
return ret;
15311557
},
15321558

1559+
glob(path, pattern) {
1560+
return glob(path, pattern, false);
1561+
},
1562+
15331563
sep: '/',
15341564
delimiter: ':',
15351565
win32: null,

test/parallel/test-path-glob.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const path = require('path');
6+
7+
const globs = {
8+
win32: [
9+
['foo\\bar\\baz', 'foo\\[bcr]ar\\baz', true], // Matches 'bar' or 'car' in 'foo\\bar'
10+
['foo\\bar\\baz', 'foo\\[!bcr]ar\\baz', false], // Matches anything except 'bar' or 'car' in 'foo\\bar'
11+
['foo\\bar\\baz', 'foo\\[bc-r]ar\\baz', true], // Matches 'bar' or 'car' using range in 'foo\\bar'
12+
['foo\\bar\\baz', 'foo\\*\\!bar\\*\\baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
13+
['foo\\bar1\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar1'
14+
['foo\\bar5\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar5'
15+
['foo\\barx\\baz', 'foo\\bar[a-z]\\baz', true], // Matches 'bar' followed by any lowercase letter in 'foo\\barx'
16+
['foo\\bar\\baz\\boo', 'foo\\[bc-r]ar\\baz\\*', true], // Matches 'bar' or 'car' in 'foo\\bar'
17+
['foo\\bar\\baz', 'foo/**', true], // Matches anything in 'foo'
18+
['foo\\bar\\baz', '*', false], // No match
19+
],
20+
posix: [
21+
['foo/bar/baz', 'foo/[bcr]ar/baz', true], // Matches 'bar' or 'car' in 'foo/bar'
22+
['foo/bar/baz', 'foo/[!bcr]ar/baz', false], // Matches anything except 'bar' or 'car' in 'foo/bar'
23+
['foo/bar/baz', 'foo/[bc-r]ar/baz', true], // Matches 'bar' or 'car' using range in 'foo/bar'
24+
['foo/bar/baz', 'foo/*/!bar/*/baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
25+
['foo/bar1/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar1'
26+
['foo/bar5/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar5'
27+
['foo/barx/baz', 'foo/bar[a-z]/baz', true], // Matches 'bar' followed by any lowercase letter in 'foo/barx'
28+
['foo/bar/baz/boo', 'foo/[bc-r]ar/baz/*', true], // Matches 'bar' or 'car' in 'foo/bar'
29+
['foo/bar/baz', 'foo/**', true], // Matches anything in 'foo'
30+
['foo/bar/baz', '*', false], // No match
31+
],
32+
};
33+
34+
35+
for (const [platform, platformGlobs] of Object.entries(globs)) {
36+
for (const [pathStr, glob, expected] of platformGlobs) {
37+
const actual = path[platform].glob(pathStr, glob);
38+
assert.strictEqual(actual, expected, `Expected ${pathStr} to ` + (expected ? '' : 'not ') + `match ${glob} on ${platform}`);
39+
}
40+
}
41+
42+
// Test for non-string input
43+
assert.throws(() => path.glob(123, 'foo/bar/baz'), /.*must be of type string.*/);
44+
assert.throws(() => path.glob('foo/bar/baz', 123), /.*must be of type string.*/);

0 commit comments

Comments
 (0)