387 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			387 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var regexNot = require('regex-not'); | ||
|  | var toRegex = require('to-regex'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Characters to use in negation regex (we want to "not" match | ||
|  |  * characters that are matched by other parsers) | ||
|  |  */ | ||
|  | 
 | ||
|  | var cached; | ||
|  | var NOT_REGEX = '[\\[!*+?$^"\'.\\\\/]+'; | ||
|  | var not = createTextRegex(NOT_REGEX); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Nanomatch parsers | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports = function(nanomatch, options) { | ||
|  |   var parser = nanomatch.parser; | ||
|  |   var opts = parser.options; | ||
|  | 
 | ||
|  |   parser.state = { | ||
|  |     slashes: 0, | ||
|  |     paths: [] | ||
|  |   }; | ||
|  | 
 | ||
|  |   parser.ast.state = parser.state; | ||
|  |   parser | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Beginning-of-string | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('prefix', function() { | ||
|  |       if (this.parsed) return; | ||
|  |       var m = this.match(/^\.[\\/]/); | ||
|  |       if (!m) return; | ||
|  |       this.state.strictOpen = !!this.options.strictOpen; | ||
|  |       this.state.addPrefix = true; | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Escape: "\\." | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('escape', function() { | ||
|  |       if (this.isInside('bracket')) return; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^(?:\\(.)|([$^]))/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'escape', | ||
|  |         val: m[2] || m[1] | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Quoted strings | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('quoted', function() { | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^["']/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       var quote = m[0]; | ||
|  |       if (this.input.indexOf(quote) === -1) { | ||
|  |         return pos({ | ||
|  |           type: 'escape', | ||
|  |           val: quote | ||
|  |         }); | ||
|  |       } | ||
|  | 
 | ||
|  |       var tok = advanceTo(this.input, quote); | ||
|  |       this.consume(tok.len); | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'quoted', | ||
|  |         val: tok.esc | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Negations: "!" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('not', function() { | ||
|  |       var parsed = this.parsed; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(this.notRegex || /^!+/); | ||
|  |       if (!m) return; | ||
|  |       var val = m[0]; | ||
|  | 
 | ||
|  |       var isNegated = (val.length % 2) === 1; | ||
|  |       if (parsed === '' && !isNegated) { | ||
|  |         val = ''; | ||
|  |       } | ||
|  | 
 | ||
|  |       // if nothing has been parsed, we know `!` is at the start,
 | ||
|  |       // so we need to wrap the result in a negation regex
 | ||
|  |       if (parsed === '' && isNegated && this.options.nonegate !== true) { | ||
|  |         this.bos.val = '(?!^(?:'; | ||
|  |         this.append = ')$).*'; | ||
|  |         val = ''; | ||
|  |       } | ||
|  |       return pos({ | ||
|  |         type: 'not', | ||
|  |         val: val | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Dot: "." | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('dot', function() { | ||
|  |       var parsed = this.parsed; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\.+/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       var val = m[0]; | ||
|  |       this.state.dot = val === '.' && (parsed === '' || parsed.slice(-1) === '/'); | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'dot', | ||
|  |         dotfiles: this.state.dot, | ||
|  |         val: val | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Plus: "+" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('plus', /^\+(?!\()/) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Question mark: "?" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('qmark', function() { | ||
|  |       var parsed = this.parsed; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\?+(?!\()/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       this.state.metachar = true; | ||
|  |       this.state.qmark = true; | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'qmark', | ||
|  |         parsed: parsed, | ||
|  |         val: m[0] | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Globstar: "**" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('globstar', function() { | ||
|  |       var parsed = this.parsed; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\*{2}(?![*(])(?=[,)/]|$)/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       var type = opts.noglobstar !== true ? 'globstar' : 'star'; | ||
|  |       var node = pos({type: type, parsed: parsed}); | ||
|  |       this.state.metachar = true; | ||
|  | 
 | ||
|  |       while (this.input.slice(0, 4) === '/**/') { | ||
|  |         this.input = this.input.slice(3); | ||
|  |       } | ||
|  | 
 | ||
|  |       node.isInside = { | ||
|  |         brace: this.isInside('brace'), | ||
|  |         paren: this.isInside('paren') | ||
|  |       }; | ||
|  | 
 | ||
|  |       if (type === 'globstar') { | ||
|  |         this.state.globstar = true; | ||
|  |         node.val = '**'; | ||
|  | 
 | ||
|  |       } else { | ||
|  |         this.state.star = true; | ||
|  |         node.val = '*'; | ||
|  |       } | ||
|  | 
 | ||
|  |       return node; | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Star: "*" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('star', function() { | ||
|  |       var pos = this.position(); | ||
|  |       var starRe = /^(?:\*(?![*(])|[*]{3,}(?!\()|[*]{2}(?![(/]|$)|\*(?=\*\())/; | ||
|  |       var m = this.match(starRe); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       this.state.metachar = true; | ||
|  |       this.state.star = true; | ||
|  |       return pos({ | ||
|  |         type: 'star', | ||
|  |         val: m[0] | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Slash: "/" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('slash', function() { | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\//); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       this.state.slashes++; | ||
|  |       return pos({ | ||
|  |         type: 'slash', | ||
|  |         val: m[0] | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Backslash: "\\" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('backslash', function() { | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\\(?![*+?(){}[\]'"])/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       var val = m[0]; | ||
|  | 
 | ||
|  |       if (this.isInside('bracket')) { | ||
|  |         val = '\\'; | ||
|  |       } else if (val.length > 1) { | ||
|  |         val = '\\\\'; | ||
|  |       } | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'backslash', | ||
|  |         val: val | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Square: "[.]" | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('square', function() { | ||
|  |       if (this.isInside('bracket')) return; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^\[([^!^\\])\]/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'square', | ||
|  |         val: m[1] | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Brackets: "[...]" (basic, this can be overridden by other parsers) | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('bracket', function() { | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(/^(?:\[([!^]?)([^\]]+|\]-)(\]|[^*+?]+)|\[)/); | ||
|  |       if (!m) return; | ||
|  | 
 | ||
|  |       var val = m[0]; | ||
|  |       var negated = m[1] ? '^' : ''; | ||
|  |       var inner = (m[2] || '').replace(/\\\\+/, '\\\\'); | ||
|  |       var close = m[3] || ''; | ||
|  | 
 | ||
|  |       if (m[2] && inner.length < m[2].length) { | ||
|  |         val = val.replace(/\\\\+/, '\\\\'); | ||
|  |       } | ||
|  | 
 | ||
|  |       var esc = this.input.slice(0, 2); | ||
|  |       if (inner === '' && esc === '\\]') { | ||
|  |         inner += esc; | ||
|  |         this.consume(2); | ||
|  | 
 | ||
|  |         var str = this.input; | ||
|  |         var idx = -1; | ||
|  |         var ch; | ||
|  | 
 | ||
|  |         while ((ch = str[++idx])) { | ||
|  |           this.consume(1); | ||
|  |           if (ch === ']') { | ||
|  |             close = ch; | ||
|  |             break; | ||
|  |           } | ||
|  |           inner += ch; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'bracket', | ||
|  |         val: val, | ||
|  |         escaped: close !== ']', | ||
|  |         negated: negated, | ||
|  |         inner: inner, | ||
|  |         close: close | ||
|  |       }); | ||
|  |     }) | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Text | ||
|  |      */ | ||
|  | 
 | ||
|  |     .capture('text', function() { | ||
|  |       if (this.isInside('bracket')) return; | ||
|  |       var pos = this.position(); | ||
|  |       var m = this.match(not); | ||
|  |       if (!m || !m[0]) return; | ||
|  | 
 | ||
|  |       return pos({ | ||
|  |         type: 'text', | ||
|  |         val: m[0] | ||
|  |       }); | ||
|  |     }); | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Allow custom parsers to be passed on options | ||
|  |    */ | ||
|  | 
 | ||
|  |   if (options && typeof options.parsers === 'function') { | ||
|  |     options.parsers(nanomatch.parser); | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Advance to the next non-escaped character | ||
|  |  */ | ||
|  | 
 | ||
|  | function advanceTo(input, endChar) { | ||
|  |   var ch = input.charAt(0); | ||
|  |   var tok = { len: 1, val: '', esc: '' }; | ||
|  |   var idx = 0; | ||
|  | 
 | ||
|  |   function advance() { | ||
|  |     if (ch !== '\\') { | ||
|  |       tok.esc += '\\' + ch; | ||
|  |       tok.val += ch; | ||
|  |     } | ||
|  | 
 | ||
|  |     ch = input.charAt(++idx); | ||
|  |     tok.len++; | ||
|  | 
 | ||
|  |     if (ch === '\\') { | ||
|  |       advance(); | ||
|  |       advance(); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   while (ch && ch !== endChar) { | ||
|  |     advance(); | ||
|  |   } | ||
|  |   return tok; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Create text regex | ||
|  |  */ | ||
|  | 
 | ||
|  | function createTextRegex(pattern) { | ||
|  |   if (cached) return cached; | ||
|  |   var opts = {contains: true, strictClose: false}; | ||
|  |   var not = regexNot.create(pattern, opts); | ||
|  |   var re = toRegex('^(?:[*]\\((?=.)|' + not + ')', opts); | ||
|  |   return (cached = re); | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Expose negation string | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports.not = NOT_REGEX; |