path: add matchGlob method

PR-URL: https://github.com/nodejs/node/pull/52881
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
Aviv Keller 2024-06-23 01:08:59 -05:00 committed by GitHub
parent 78ea6cee1b
commit 92a25abca9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 99 additions and 0 deletions

View File

@ -279,6 +279,29 @@ path.format({
// Returns: 'C:\\path\\dir\\file.txt'
```
## `path.matchesGlob(path, pattern)`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
* `path` {string} The path to glob-match against.
* `pattern` {string} The glob to check the path against.
* Returns: {boolean} Whether or not the `path` matched the `pattern`.
The `path.matchesGlob()` method determines if `path` matches the `pattern`.
For example:
```js
path.matchesGlob('/foo/bar', '/foo/*'); // true
path.matchesGlob('/foo/bar*', 'foo/bird'); // false
```
A [`TypeError`][] is thrown if `path` or `pattern` are not strings.
## `path.isAbsolute(path)`
<!-- YAML

View File

@ -47,7 +47,15 @@ const {
validateString,
} = require('internal/validators');
const {
getLazy,
emitExperimentalWarning,
} = require('internal/util');
const lazyMinimatch = getLazy(() => require('internal/deps/minimatch/index'));
const platformIsWin32 = (process.platform === 'win32');
const platformIsOSX = (process.platform === 'darwin');
function isPathSeparator(code) {
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
@ -153,6 +161,22 @@ function _format(sep, pathObject) {
return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`;
}
function glob(path, pattern, windows) {
emitExperimentalWarning('glob');
validateString(path, 'path');
validateString(pattern, 'pattern');
return lazyMinimatch().minimatch(path, pattern, {
__proto__: null,
nocase: platformIsOSX || platformIsWin32,
windowsPathsNoEscape: true,
nonegate: true,
nocomment: true,
optimizationLevel: 2,
platform: windows ? 'win32' : 'posix',
nocaseMagicOnly: true,
});
}
const win32 = {
/**
* path.resolve([from ...], to)
@ -1065,6 +1089,10 @@ const win32 = {
return ret;
},
matchesGlob(path, pattern) {
return glob(path, pattern, true);
},
sep: '\\',
delimiter: ';',
win32: null,
@ -1530,6 +1558,10 @@ const posix = {
return ret;
},
matchesGlob(path, pattern) {
return glob(path, pattern, false);
},
sep: '/',
delimiter: ':',
win32: null,

View File

@ -0,0 +1,44 @@
'use strict';
require('../common');
const assert = require('assert');
const path = require('path');
const globs = {
win32: [
['foo\\bar\\baz', 'foo\\[bcr]ar\\baz', true], // Matches 'bar' or 'car' in 'foo\\bar'
['foo\\bar\\baz', 'foo\\[!bcr]ar\\baz', false], // Matches anything except 'bar' or 'car' in 'foo\\bar'
['foo\\bar\\baz', 'foo\\[bc-r]ar\\baz', true], // Matches 'bar' or 'car' using range in 'foo\\bar'
['foo\\bar\\baz', 'foo\\*\\!bar\\*\\baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
['foo\\bar1\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar1'
['foo\\bar5\\baz', 'foo\\bar[0-9]\\baz', true], // Matches 'bar' followed by any digit in 'foo\\bar5'
['foo\\barx\\baz', 'foo\\bar[a-z]\\baz', true], // Matches 'bar' followed by any lowercase letter in 'foo\\barx'
['foo\\bar\\baz\\boo', 'foo\\[bc-r]ar\\baz\\*', true], // Matches 'bar' or 'car' in 'foo\\bar'
['foo\\bar\\baz', 'foo/**', true], // Matches anything in 'foo'
['foo\\bar\\baz', '*', false], // No match
],
posix: [
['foo/bar/baz', 'foo/[bcr]ar/baz', true], // Matches 'bar' or 'car' in 'foo/bar'
['foo/bar/baz', 'foo/[!bcr]ar/baz', false], // Matches anything except 'bar' or 'car' in 'foo/bar'
['foo/bar/baz', 'foo/[bc-r]ar/baz', true], // Matches 'bar' or 'car' using range in 'foo/bar'
['foo/bar/baz', 'foo/*/!bar/*/baz', false], // Matches anything with 'foo' and 'baz' but not 'bar' in between
['foo/bar1/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar1'
['foo/bar5/baz', 'foo/bar[0-9]/baz', true], // Matches 'bar' followed by any digit in 'foo/bar5'
['foo/barx/baz', 'foo/bar[a-z]/baz', true], // Matches 'bar' followed by any lowercase letter in 'foo/barx'
['foo/bar/baz/boo', 'foo/[bc-r]ar/baz/*', true], // Matches 'bar' or 'car' in 'foo/bar'
['foo/bar/baz', 'foo/**', true], // Matches anything in 'foo'
['foo/bar/baz', '*', false], // No match
],
};
for (const [platform, platformGlobs] of Object.entries(globs)) {
for (const [pathStr, glob, expected] of platformGlobs) {
const actual = path[platform].matchesGlob(pathStr, glob);
assert.strictEqual(actual, expected, `Expected ${pathStr} to ` + (expected ? '' : 'not ') + `match ${glob} on ${platform}`);
}
}
// Test for non-string input
assert.throws(() => path.matchesGlob(123, 'foo/bar/baz'), /.*must be of type string.*/);
assert.throws(() => path.matchesGlob('foo/bar/baz', 123), /.*must be of type string.*/);