2 * extglob <https://github.com/jonschlinkert/extglob>
4 * Copyright (c) 2015, Jon Schlinkert.
5 * Licensed under the MIT License.
14 var isExtglob = require('is-extglob');
21 module.exports = extglob;
24 * Convert the given extglob `string` to a regex-compatible
28 * var extglob = require('extglob');
29 * extglob('!(a?(b))');
30 * //=> '(?!a(?:b)?)[^/]*?'
33 * @param {String} `str` The string to convert.
34 * @param {Object} `options`
35 * @option {Boolean} [options] `esc` If `false` special characters will not be escaped. Defaults to `true`.
36 * @option {Boolean} [options] `regex` If `true` a regular expression is returned instead of a string.
42 function extglob(str, opts) {
46 // fix common character reversals
47 // '*!(.js)' => '*.!(js)'
48 str = str.replace(/!\(([^\w*()])/g, '$1!(');
50 // support file extension negation
51 str = str.replace(/([*\/])\.!\([*]\)/g, function (m, ch) {
53 return escape('\\/[^.]+');
55 return escape('[^.]+');
58 // create a unique key for caching by
59 // combining the string and options
61 + String(!!opts.regex)
62 + String(!!opts.contains)
63 + String(!!opts.escape);
65 if (cache.hasOwnProperty(key)) {
69 if (!(re instanceof RegExp)) {
76 while (m = re.exec(str)) {
83 var id = '__EXTGLOB_' + (i++) + '__';
84 // use the prefix of the _last_ (outtermost) pattern
85 o[id] = wrap(inner, prefix, opts.escape);
86 str = str.split(m[0]).join(id);
89 var keys = Object.keys(o);
90 var len = keys.length;
92 // we have to loop again to allow us to convert
93 // patterns in reverse order (starting with the
94 // innermost/last pattern first)
97 str = str.split(prop).join(o[prop]);
100 var result = opts.regex
101 ? toRegex(str, opts.contains, opts.negate)
104 result = result.split('.').join('\\.');
106 // cache the result and return it
107 return (cache[key] = result);
111 * Convert `string` to a regex string.
113 * @param {String} `str`
114 * @param {String} `prefix` Character that determines how to wrap the string.
115 * @param {Boolean} `esc` If `false` special characters will not be escaped. Defaults to `true`.
119 function wrap(inner, prefix, esc) {
120 if (esc) inner = escape(inner);
124 return '(?!' + inner + ')[^/]' + (esc ? '%%%~' : '*?');
126 return '(?:' + inner + ')';
128 return '(?:' + inner + ')+';
130 return '(?:' + inner + ')' + (esc ? '%%' : '*')
132 return '(?:' + inner + '|)';
138 function escape(str) {
139 str = str.split('*').join('[^/]%%%~');
140 str = str.split('.').join('\\.');
149 return /(\\?[@?!+*$]\\?)(\(([^()]*?)\))/;
156 function negate(str) {
157 return '(?!^' + str + ').*$';
161 * Create the regex to do the matching. If
162 * the leading character in the `pattern` is `!`
163 * a negation regex is returned.
165 * @param {String} `pattern`
166 * @param {Boolean} `contains` Allow loose matching.
167 * @param {Boolean} `isNegated` True if the pattern is a negation pattern.
170 function toRegex(pattern, contains, isNegated) {
171 var prefix = contains ? '^' : '';
172 var after = contains ? '$' : '';
173 pattern = ('(?:' + pattern + ')' + after);
175 pattern = prefix + negate(pattern);
177 return new RegExp(prefix + pattern);