UNPKG

nanomatch

Version:

Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash 4.3 wildcard support only (no support for exglobs, posix brackets or braces)

387 lines (310 loc) 7.63 kB
'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;