You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
9.0 KiB
391 lines
9.0 KiB
'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; |
|
}; |
|
|
|
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;
|
|
|