Spaces:
Running
Running
; | |
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; | |
}; | |
const depth = token => { | |
if (token.isPrefix !== true) { | |
token.depth = token.isGlobstar ? Infinity : 1; | |
} | |
}; | |
/** | |
* 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), `negated` (true if the path starts with `!` but not | |
* with `!(`) and `negatedExtglob` (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 | |
*/ | |
const scan = (input, options) => { | |
const opts = options || {}; | |
const length = input.length - 1; | |
const scanToEnd = opts.parts === true || opts.scanToEnd === true; | |
const slashes = []; | |
const tokens = []; | |
const parts = []; | |
let str = input; | |
let index = -1; | |
let start = 0; | |
let lastIndex = 0; | |
let isBrace = false; | |
let isBracket = false; | |
let isGlob = false; | |
let isExtglob = false; | |
let isGlobstar = false; | |
let braceEscaped = false; | |
let backslashes = false; | |
let negated = false; | |
let negatedExtglob = false; | |
let finished = false; | |
let braces = 0; | |
let prev; | |
let code; | |
let token = { value: '', depth: 0, isGlob: false }; | |
const eos = () => index >= length; | |
const peek = () => str.charCodeAt(index + 1); | |
const advance = () => { | |
prev = code; | |
return str.charCodeAt(++index); | |
}; | |
while (index < length) { | |
code = advance(); | |
let next; | |
if (code === CHAR_BACKWARD_SLASH) { | |
backslashes = token.backslashes = true; | |
code = advance(); | |
if (code === CHAR_LEFT_CURLY_BRACE) { | |
braceEscaped = true; | |
} | |
continue; | |
} | |
if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { | |
braces++; | |
while (eos() !== true && (code = advance())) { | |
if (code === CHAR_BACKWARD_SLASH) { | |
backslashes = token.backslashes = true; | |
advance(); | |
continue; | |
} | |
if (code === CHAR_LEFT_CURLY_BRACE) { | |
braces++; | |
continue; | |
} | |
if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) { | |
isBrace = token.isBrace = true; | |
isGlob = token.isGlob = true; | |
finished = true; | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (braceEscaped !== true && code === CHAR_COMMA) { | |
isBrace = token.isBrace = true; | |
isGlob = token.isGlob = true; | |
finished = true; | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (code === CHAR_RIGHT_CURLY_BRACE) { | |
braces--; | |
if (braces === 0) { | |
braceEscaped = false; | |
isBrace = token.isBrace = true; | |
finished = true; | |
break; | |
} | |
} | |
} | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (code === CHAR_FORWARD_SLASH) { | |
slashes.push(index); | |
tokens.push(token); | |
token = { value: '', depth: 0, isGlob: false }; | |
if (finished === true) continue; | |
if (prev === CHAR_DOT && index === (start + 1)) { | |
start += 2; | |
continue; | |
} | |
lastIndex = index + 1; | |
continue; | |
} | |
if (opts.noext !== true) { | |
const isExtglobChar = code === CHAR_PLUS | |
|| code === CHAR_AT | |
|| code === CHAR_ASTERISK | |
|| code === CHAR_QUESTION_MARK | |
|| code === CHAR_EXCLAMATION_MARK; | |
if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) { | |
isGlob = token.isGlob = true; | |
isExtglob = token.isExtglob = true; | |
finished = true; | |
if (code === CHAR_EXCLAMATION_MARK && index === start) { | |
negatedExtglob = true; | |
} | |
if (scanToEnd === true) { | |
while (eos() !== true && (code = advance())) { | |
if (code === CHAR_BACKWARD_SLASH) { | |
backslashes = token.backslashes = true; | |
code = advance(); | |
continue; | |
} | |
if (code === CHAR_RIGHT_PARENTHESES) { | |
isGlob = token.isGlob = true; | |
finished = true; | |
break; | |
} | |
} | |
continue; | |
} | |
break; | |
} | |
} | |
if (code === CHAR_ASTERISK) { | |
if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; | |
isGlob = token.isGlob = true; | |
finished = true; | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (code === CHAR_QUESTION_MARK) { | |
isGlob = token.isGlob = true; | |
finished = true; | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (code === CHAR_LEFT_SQUARE_BRACKET) { | |
while (eos() !== true && (next = advance())) { | |
if (next === CHAR_BACKWARD_SLASH) { | |
backslashes = token.backslashes = true; | |
advance(); | |
continue; | |
} | |
if (next === CHAR_RIGHT_SQUARE_BRACKET) { | |
isBracket = token.isBracket = true; | |
isGlob = token.isGlob = true; | |
finished = true; | |
break; | |
} | |
} | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) { | |
negated = token.negated = true; | |
start++; | |
continue; | |
} | |
if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { | |
isGlob = token.isGlob = true; | |
if (scanToEnd === true) { | |
while (eos() !== true && (code = advance())) { | |
if (code === CHAR_LEFT_PARENTHESES) { | |
backslashes = token.backslashes = true; | |
code = advance(); | |
continue; | |
} | |
if (code === CHAR_RIGHT_PARENTHESES) { | |
finished = true; | |
break; | |
} | |
} | |
continue; | |
} | |
break; | |
} | |
if (isGlob === true) { | |
finished = true; | |
if (scanToEnd === true) { | |
continue; | |
} | |
break; | |
} | |
} | |
if (opts.noext === true) { | |
isExtglob = false; | |
isGlob = false; | |
} | |
let base = str; | |
let prefix = ''; | |
let glob = ''; | |
if (start > 0) { | |
prefix = str.slice(0, start); | |
str = str.slice(start); | |
lastIndex -= start; | |
} | |
if (base && isGlob === true && lastIndex > 0) { | |
base = str.slice(0, lastIndex); | |
glob = str.slice(lastIndex); | |
} else if (isGlob === true) { | |
base = ''; | |
glob = str; | |
} else { | |
base = str; | |
} | |
if (base && base !== '' && base !== '/' && base !== str) { | |
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); | |
} | |
} | |
const state = { | |
prefix, | |
input, | |
start, | |
base, | |
glob, | |
isBrace, | |
isBracket, | |
isGlob, | |
isExtglob, | |
isGlobstar, | |
negated, | |
negatedExtglob | |
}; | |
if (opts.tokens === true) { | |
state.maxDepth = 0; | |
if (!isPathSeparator(code)) { | |
tokens.push(token); | |
} | |
state.tokens = tokens; | |
} | |
if (opts.parts === true || opts.tokens === true) { | |
let prevIndex; | |
for (let idx = 0; idx < slashes.length; idx++) { | |
const n = prevIndex ? prevIndex + 1 : start; | |
const i = slashes[idx]; | |
const value = input.slice(n, i); | |
if (opts.tokens) { | |
if (idx === 0 && start !== 0) { | |
tokens[idx].isPrefix = true; | |
tokens[idx].value = prefix; | |
} else { | |
tokens[idx].value = value; | |
} | |
depth(tokens[idx]); | |
state.maxDepth += tokens[idx].depth; | |
} | |
if (idx !== 0 || value !== '') { | |
parts.push(value); | |
} | |
prevIndex = i; | |
} | |
if (prevIndex && prevIndex + 1 < input.length) { | |
const value = input.slice(prevIndex + 1); | |
parts.push(value); | |
if (opts.tokens) { | |
tokens[tokens.length - 1].value = value; | |
depth(tokens[tokens.length - 1]); | |
state.maxDepth += tokens[tokens.length - 1].depth; | |
} | |
} | |
state.slashes = slashes; | |
state.parts = parts; | |
} | |
return state; | |
}; | |
module.exports = scan; | |