/*! * extglob * * Copyright (c) 2015, Jon Schlinkert. * Licensed under the MIT License. */ 'use strict'; /** * Module dependencies */ var isExtglob = require('is-extglob'); var re, cache = {}; /** * Expose `extglob` */ module.exports = extglob; /** * Convert the given extglob `string` to a regex-compatible * string. * * ```js * var extglob = require('extglob'); * extglob('!(a?(b))'); * //=> '(?!a(?:b)?)[^/]*?' * ``` * * @param {String} `str` The string to convert. * @param {Object} `options` * @option {Boolean} [options] `esc` If `false` special characters will not be escaped. Defaults to `true`. * @option {Boolean} [options] `regex` If `true` a regular expression is returned instead of a string. * @return {String} * @api public */ function extglob(str, opts) { opts = opts || {}; var o = {}, i = 0; // fix common character reversals // '*!(.js)' => '*.!(js)' str = str.replace(/!\(([^\w*()])/g, '$1!('); // support file extension negation str = str.replace(/([*\/])\.!\([*]\)/g, function (m, ch) { if (ch === '/') { return escape('\\/[^.]+'); } return escape('[^.]+'); }); // create a unique key for caching by // combining the string and options var key = str + String(!!opts.regex) + String(!!opts.contains) + String(!!opts.escape); if (cache.hasOwnProperty(key)) { return cache[key]; } if (!(re instanceof RegExp)) { re = regex(); } opts.negate = false; var m; while (m = re.exec(str)) { var prefix = m[1]; var inner = m[3]; if (prefix === '!') { opts.negate = true; } var id = '__EXTGLOB_' + (i++) + '__'; // use the prefix of the _last_ (outtermost) pattern o[id] = wrap(inner, prefix, opts.escape); str = str.split(m[0]).join(id); } var keys = Object.keys(o); var len = keys.length; // we have to loop again to allow us to convert // patterns in reverse order (starting with the // innermost/last pattern first) while (len--) { var prop = keys[len]; str = str.split(prop).join(o[prop]); } var result = opts.regex ? toRegex(str, opts.contains, opts.negate) : str; result = result.split('.').join('\\.'); // cache the result and return it return (cache[key] = result); } /** * Convert `string` to a regex string. * * @param {String} `str` * @param {String} `prefix` Character that determines how to wrap the string. * @param {Boolean} `esc` If `false` special characters will not be escaped. Defaults to `true`. * @return {String} */ function wrap(inner, prefix, esc) { if (esc) inner = escape(inner); switch (prefix) { case '!': return '(?!' + inner + ')[^/]' + (esc ? '%%%~' : '*?'); case '@': return '(?:' + inner + ')'; case '+': return '(?:' + inner + ')+'; case '*': return '(?:' + inner + ')' + (esc ? '%%' : '*') case '?': return '(?:' + inner + '|)'; default: return inner; } } function escape(str) { str = str.split('*').join('[^/]%%%~'); str = str.split('.').join('\\.'); return str; } /** * extglob regex. */ function regex() { return /(\\?[@?!+*$]\\?)(\(([^()]*?)\))/; } /** * Negation regex */ function negate(str) { return '(?!^' + str + ').*$'; } /** * Create the regex to do the matching. If * the leading character in the `pattern` is `!` * a negation regex is returned. * * @param {String} `pattern` * @param {Boolean} `contains` Allow loose matching. * @param {Boolean} `isNegated` True if the pattern is a negation pattern. */ function toRegex(pattern, contains, isNegated) { var prefix = contains ? '^' : ''; var after = contains ? '$' : ''; pattern = ('(?:' + pattern + ')' + after); if (isNegated) { pattern = prefix + negate(pattern); } return new RegExp(prefix + pattern); }