223 lines
5.0 KiB
JavaScript
223 lines
5.0 KiB
JavaScript
'use strict';
|
|
|
|
const utils = require('./utils');
|
|
const {
|
|
CHAR_ASTERISK, /* * */
|
|
CHAR_AT, /* @ */
|
|
CHAR_BACKWARD_SLASH, /* \ */
|
|
CHAR_COMMA, /* , */
|
|
CHAR_DOT, /* . */
|
|
CHAR_EXCLAMATION_MARK, /* ! */
|
|
CHAR_FORWARD_SLASH, /* / */
|
|
CHAR_LEFT_CURLY_BRACE, /* { */
|
|
CHAR_LEFT_PARENTHESES, /* ( */
|
|
CHAR_LEFT_SQUARE_BRACKET, /* [ */
|
|
CHAR_PLUS, /* + */
|
|
CHAR_QUESTION_MARK, /* ? */
|
|
CHAR_RIGHT_CURLY_BRACE, /* } */
|
|
CHAR_RIGHT_PARENTHESES, /* ) */
|
|
CHAR_RIGHT_SQUARE_BRACKET /* ] */
|
|
} = require('./constants');
|
|
|
|
const isPathSeparator = code => {
|
|
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
|
};
|
|
|
|
/**
|
|
* Quickly scans a glob pattern and returns an object with a handful of
|
|
* useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
|
|
* `glob` (the actual pattern), and `negated` (true if the path starts with `!`).
|
|
*
|
|
* ```js
|
|
* const pm = require('picomatch');
|
|
* console.log(pm.scan('foo/bar/*.js'));
|
|
* { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
|
|
* ```
|
|
* @param {String} `str`
|
|
* @param {Object} `options`
|
|
* @return {Object} Returns an object with tokens and regex source string.
|
|
* @api public
|
|
*/
|
|
|
|
module.exports = (input, options) => {
|
|
const opts = options || {};
|
|
const length = input.length - 1;
|
|
let index = -1;
|
|
let start = 0;
|
|
let lastIndex = 0;
|
|
let isGlob = false;
|
|
let backslashes = false;
|
|
let negated = false;
|
|
let braces = 0;
|
|
let prev;
|
|
let code;
|
|
|
|
let braceEscaped = false;
|
|
|
|
const eos = () => index >= length;
|
|
const advance = () => {
|
|
prev = code;
|
|
return input.charCodeAt(++index);
|
|
};
|
|
|
|
while (index < length) {
|
|
code = advance();
|
|
let next;
|
|
|
|
if (code === CHAR_BACKWARD_SLASH) {
|
|
backslashes = true;
|
|
next = advance();
|
|
|
|
if (next === CHAR_LEFT_CURLY_BRACE) {
|
|
braceEscaped = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
|
|
braces++;
|
|
|
|
while (!eos() && (next = advance())) {
|
|
if (next === CHAR_BACKWARD_SLASH) {
|
|
backslashes = true;
|
|
next = advance();
|
|
continue;
|
|
}
|
|
|
|
if (next === CHAR_LEFT_CURLY_BRACE) {
|
|
braces++;
|
|
continue;
|
|
}
|
|
|
|
if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
|
|
if (!braceEscaped && next === CHAR_COMMA) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
|
|
if (next === CHAR_RIGHT_CURLY_BRACE) {
|
|
braces--;
|
|
if (braces === 0) {
|
|
braceEscaped = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (code === CHAR_FORWARD_SLASH) {
|
|
if (prev === CHAR_DOT && index === (start + 1)) {
|
|
start += 2;
|
|
continue;
|
|
}
|
|
|
|
lastIndex = index + 1;
|
|
continue;
|
|
}
|
|
|
|
if (code === CHAR_ASTERISK) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
|
|
if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
|
|
if (code === CHAR_LEFT_SQUARE_BRACKET) {
|
|
while (!eos() && (next = advance())) {
|
|
if (next === CHAR_BACKWARD_SLASH) {
|
|
backslashes = true;
|
|
next = advance();
|
|
continue;
|
|
}
|
|
|
|
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const isExtglobChar = code === CHAR_PLUS
|
|
|| code === CHAR_AT
|
|
|| code === CHAR_EXCLAMATION_MARK;
|
|
|
|
if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
|
|
if (!opts.nonegate && code === CHAR_EXCLAMATION_MARK && index === start) {
|
|
negated = true;
|
|
start++;
|
|
continue;
|
|
}
|
|
|
|
if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
|
|
while (!eos() && (code = advance())) {
|
|
if (code === CHAR_BACKWARD_SLASH) {
|
|
backslashes = true;
|
|
code = advance();
|
|
continue;
|
|
}
|
|
|
|
if (code === CHAR_RIGHT_PARENTHESES) {
|
|
isGlob = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isGlob) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (opts.noext === true) {
|
|
isGlob = false;
|
|
}
|
|
|
|
let prefix = '';
|
|
const orig = input;
|
|
let base = input;
|
|
let glob = '';
|
|
|
|
if (start > 0) {
|
|
prefix = input.slice(0, start);
|
|
input = input.slice(start);
|
|
lastIndex -= start;
|
|
}
|
|
|
|
if (base && isGlob === true && lastIndex > 0) {
|
|
base = input.slice(0, lastIndex);
|
|
glob = input.slice(lastIndex);
|
|
} else if (isGlob === true) {
|
|
base = '';
|
|
glob = input;
|
|
} else {
|
|
base = input;
|
|
}
|
|
|
|
if (base && base !== '' && base !== '/' && base !== input) {
|
|
if (isPathSeparator(base.charCodeAt(base.length - 1))) {
|
|
base = base.slice(0, -1);
|
|
}
|
|
}
|
|
|
|
if (opts.unescape === true) {
|
|
if (glob) glob = utils.removeBackslashes(glob);
|
|
|
|
if (base && backslashes === true) {
|
|
base = utils.removeBackslashes(base);
|
|
}
|
|
}
|
|
|
|
return { prefix, input: orig, base, glob, negated, isGlob };
|
|
};
|