215 lines
7.0 KiB
JavaScript
215 lines
7.0 KiB
JavaScript
|
// this is just a very light wrapper around 2 arrays with an offset index
|
||
|
import { GLOBSTAR } from 'minimatch';
|
||
|
const isPatternList = (pl) => pl.length >= 1;
|
||
|
const isGlobList = (gl) => gl.length >= 1;
|
||
|
/**
|
||
|
* An immutable-ish view on an array of glob parts and their parsed
|
||
|
* results
|
||
|
*/
|
||
|
export class Pattern {
|
||
|
#patternList;
|
||
|
#globList;
|
||
|
#index;
|
||
|
length;
|
||
|
#platform;
|
||
|
#rest;
|
||
|
#globString;
|
||
|
#isDrive;
|
||
|
#isUNC;
|
||
|
#isAbsolute;
|
||
|
#followGlobstar = true;
|
||
|
constructor(patternList, globList, index, platform) {
|
||
|
if (!isPatternList(patternList)) {
|
||
|
throw new TypeError('empty pattern list');
|
||
|
}
|
||
|
if (!isGlobList(globList)) {
|
||
|
throw new TypeError('empty glob list');
|
||
|
}
|
||
|
if (globList.length !== patternList.length) {
|
||
|
throw new TypeError('mismatched pattern list and glob list lengths');
|
||
|
}
|
||
|
this.length = patternList.length;
|
||
|
if (index < 0 || index >= this.length) {
|
||
|
throw new TypeError('index out of range');
|
||
|
}
|
||
|
this.#patternList = patternList;
|
||
|
this.#globList = globList;
|
||
|
this.#index = index;
|
||
|
this.#platform = platform;
|
||
|
// normalize root entries of absolute patterns on initial creation.
|
||
|
if (this.#index === 0) {
|
||
|
// c: => ['c:/']
|
||
|
// C:/ => ['C:/']
|
||
|
// C:/x => ['C:/', 'x']
|
||
|
// //host/share => ['//host/share/']
|
||
|
// //host/share/ => ['//host/share/']
|
||
|
// //host/share/x => ['//host/share/', 'x']
|
||
|
// /etc => ['/', 'etc']
|
||
|
// / => ['/']
|
||
|
if (this.isUNC()) {
|
||
|
// '' / '' / 'host' / 'share'
|
||
|
const [p0, p1, p2, p3, ...prest] = this.#patternList;
|
||
|
const [g0, g1, g2, g3, ...grest] = this.#globList;
|
||
|
if (prest[0] === '') {
|
||
|
// ends in /
|
||
|
prest.shift();
|
||
|
grest.shift();
|
||
|
}
|
||
|
const p = [p0, p1, p2, p3, ''].join('/');
|
||
|
const g = [g0, g1, g2, g3, ''].join('/');
|
||
|
this.#patternList = [p, ...prest];
|
||
|
this.#globList = [g, ...grest];
|
||
|
this.length = this.#patternList.length;
|
||
|
}
|
||
|
else if (this.isDrive() || this.isAbsolute()) {
|
||
|
const [p1, ...prest] = this.#patternList;
|
||
|
const [g1, ...grest] = this.#globList;
|
||
|
if (prest[0] === '') {
|
||
|
// ends in /
|
||
|
prest.shift();
|
||
|
grest.shift();
|
||
|
}
|
||
|
const p = p1 + '/';
|
||
|
const g = g1 + '/';
|
||
|
this.#patternList = [p, ...prest];
|
||
|
this.#globList = [g, ...grest];
|
||
|
this.length = this.#patternList.length;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* The first entry in the parsed list of patterns
|
||
|
*/
|
||
|
pattern() {
|
||
|
return this.#patternList[this.#index];
|
||
|
}
|
||
|
/**
|
||
|
* true of if pattern() returns a string
|
||
|
*/
|
||
|
isString() {
|
||
|
return typeof this.#patternList[this.#index] === 'string';
|
||
|
}
|
||
|
/**
|
||
|
* true of if pattern() returns GLOBSTAR
|
||
|
*/
|
||
|
isGlobstar() {
|
||
|
return this.#patternList[this.#index] === GLOBSTAR;
|
||
|
}
|
||
|
/**
|
||
|
* true if pattern() returns a regexp
|
||
|
*/
|
||
|
isRegExp() {
|
||
|
return this.#patternList[this.#index] instanceof RegExp;
|
||
|
}
|
||
|
/**
|
||
|
* The /-joined set of glob parts that make up this pattern
|
||
|
*/
|
||
|
globString() {
|
||
|
return (this.#globString =
|
||
|
this.#globString ||
|
||
|
(this.#index === 0
|
||
|
? this.isAbsolute()
|
||
|
? this.#globList[0] + this.#globList.slice(1).join('/')
|
||
|
: this.#globList.join('/')
|
||
|
: this.#globList.slice(this.#index).join('/')));
|
||
|
}
|
||
|
/**
|
||
|
* true if there are more pattern parts after this one
|
||
|
*/
|
||
|
hasMore() {
|
||
|
return this.length > this.#index + 1;
|
||
|
}
|
||
|
/**
|
||
|
* The rest of the pattern after this part, or null if this is the end
|
||
|
*/
|
||
|
rest() {
|
||
|
if (this.#rest !== undefined)
|
||
|
return this.#rest;
|
||
|
if (!this.hasMore())
|
||
|
return (this.#rest = null);
|
||
|
this.#rest = new Pattern(this.#patternList, this.#globList, this.#index + 1, this.#platform);
|
||
|
this.#rest.#isAbsolute = this.#isAbsolute;
|
||
|
this.#rest.#isUNC = this.#isUNC;
|
||
|
this.#rest.#isDrive = this.#isDrive;
|
||
|
return this.#rest;
|
||
|
}
|
||
|
/**
|
||
|
* true if the pattern represents a //unc/path/ on windows
|
||
|
*/
|
||
|
isUNC() {
|
||
|
const pl = this.#patternList;
|
||
|
return this.#isUNC !== undefined
|
||
|
? this.#isUNC
|
||
|
: (this.#isUNC =
|
||
|
this.#platform === 'win32' &&
|
||
|
this.#index === 0 &&
|
||
|
pl[0] === '' &&
|
||
|
pl[1] === '' &&
|
||
|
typeof pl[2] === 'string' &&
|
||
|
!!pl[2] &&
|
||
|
typeof pl[3] === 'string' &&
|
||
|
!!pl[3]);
|
||
|
}
|
||
|
// pattern like C:/...
|
||
|
// split = ['C:', ...]
|
||
|
// XXX: would be nice to handle patterns like `c:*` to test the cwd
|
||
|
// in c: for *, but I don't know of a way to even figure out what that
|
||
|
// cwd is without actually chdir'ing into it?
|
||
|
/**
|
||
|
* True if the pattern starts with a drive letter on Windows
|
||
|
*/
|
||
|
isDrive() {
|
||
|
const pl = this.#patternList;
|
||
|
return this.#isDrive !== undefined
|
||
|
? this.#isDrive
|
||
|
: (this.#isDrive =
|
||
|
this.#platform === 'win32' &&
|
||
|
this.#index === 0 &&
|
||
|
this.length > 1 &&
|
||
|
typeof pl[0] === 'string' &&
|
||
|
/^[a-z]:$/i.test(pl[0]));
|
||
|
}
|
||
|
// pattern = '/' or '/...' or '/x/...'
|
||
|
// split = ['', ''] or ['', ...] or ['', 'x', ...]
|
||
|
// Drive and UNC both considered absolute on windows
|
||
|
/**
|
||
|
* True if the pattern is rooted on an absolute path
|
||
|
*/
|
||
|
isAbsolute() {
|
||
|
const pl = this.#patternList;
|
||
|
return this.#isAbsolute !== undefined
|
||
|
? this.#isAbsolute
|
||
|
: (this.#isAbsolute =
|
||
|
(pl[0] === '' && pl.length > 1) ||
|
||
|
this.isDrive() ||
|
||
|
this.isUNC());
|
||
|
}
|
||
|
/**
|
||
|
* consume the root of the pattern, and return it
|
||
|
*/
|
||
|
root() {
|
||
|
const p = this.#patternList[0];
|
||
|
return typeof p === 'string' && this.isAbsolute() && this.#index === 0
|
||
|
? p
|
||
|
: '';
|
||
|
}
|
||
|
/**
|
||
|
* Check to see if the current globstar pattern is allowed to follow
|
||
|
* a symbolic link.
|
||
|
*/
|
||
|
checkFollowGlobstar() {
|
||
|
return !(this.#index === 0 ||
|
||
|
!this.isGlobstar() ||
|
||
|
!this.#followGlobstar);
|
||
|
}
|
||
|
/**
|
||
|
* Mark that the current globstar pattern is following a symbolic link
|
||
|
*/
|
||
|
markFollowGlobstar() {
|
||
|
if (this.#index === 0 || !this.isGlobstar() || !this.#followGlobstar)
|
||
|
return false;
|
||
|
this.#followGlobstar = false;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
//# sourceMappingURL=pattern.js.map
|