392 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			9.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;
 | |
| };
 | |
| 
 | |
| 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;
 |