index.js
1 'use strict'; 2 3 /** 4 * Module dependencies 5 */ 6 7 var extend = require('extend-shallow'); 8 var unique = require('array-unique'); 9 var toRegex = require('to-regex'); 10 11 /** 12 * Local dependencies 13 */ 14 15 var compilers = require('./lib/compilers'); 16 var parsers = require('./lib/parsers'); 17 var Extglob = require('./lib/extglob'); 18 var utils = require('./lib/utils'); 19 var MAX_LENGTH = 1024 * 64; 20 21 /** 22 * Convert the given `extglob` pattern into a regex-compatible string. Returns 23 * an object with the compiled result and the parsed AST. 24 * 25 * ```js 26 * var extglob = require('extglob'); 27 * console.log(extglob('*.!(*a)')); 28 * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' 29 * ``` 30 * @param {String} `pattern` 31 * @param {Object} `options` 32 * @return {String} 33 * @api public 34 */ 35 36 function extglob(pattern, options) { 37 return extglob.create(pattern, options).output; 38 } 39 40 /** 41 * Takes an array of strings and an extglob pattern and returns a new 42 * array that contains only the strings that match the pattern. 43 * 44 * ```js 45 * var extglob = require('extglob'); 46 * console.log(extglob.match(['a.a', 'a.b', 'a.c'], '*.!(*a)')); 47 * //=> ['a.b', 'a.c'] 48 * ``` 49 * @param {Array} `list` Array of strings to match 50 * @param {String} `pattern` Extglob pattern 51 * @param {Object} `options` 52 * @return {Array} Returns an array of matches 53 * @api public 54 */ 55 56 extglob.match = function(list, pattern, options) { 57 if (typeof pattern !== 'string') { 58 throw new TypeError('expected pattern to be a string'); 59 } 60 61 list = utils.arrayify(list); 62 var isMatch = extglob.matcher(pattern, options); 63 var len = list.length; 64 var idx = -1; 65 var matches = []; 66 67 while (++idx < len) { 68 var ele = list[idx]; 69 70 if (isMatch(ele)) { 71 matches.push(ele); 72 } 73 } 74 75 // if no options were passed, uniquify results and return 76 if (typeof options === 'undefined') { 77 return unique(matches); 78 } 79 80 if (matches.length === 0) { 81 if (options.failglob === true) { 82 throw new Error('no matches found for "' + pattern + '"'); 83 } 84 if (options.nonull === true || options.nullglob === true) { 85 return [pattern.split('\\').join('')]; 86 } 87 } 88 89 return options.nodupes !== false ? unique(matches) : matches; 90 }; 91 92 /** 93 * Returns true if the specified `string` matches the given 94 * extglob `pattern`. 95 * 96 * ```js 97 * var extglob = require('extglob'); 98 * 99 * console.log(extglob.isMatch('a.a', '*.!(*a)')); 100 * //=> false 101 * console.log(extglob.isMatch('a.b', '*.!(*a)')); 102 * //=> true 103 * ``` 104 * @param {String} `string` String to match 105 * @param {String} `pattern` Extglob pattern 106 * @param {String} `options` 107 * @return {Boolean} 108 * @api public 109 */ 110 111 extglob.isMatch = function(str, pattern, options) { 112 if (typeof pattern !== 'string') { 113 throw new TypeError('expected pattern to be a string'); 114 } 115 116 if (typeof str !== 'string') { 117 throw new TypeError('expected a string'); 118 } 119 120 if (pattern === str) { 121 return true; 122 } 123 124 if (pattern === '' || pattern === ' ' || pattern === '.') { 125 return pattern === str; 126 } 127 128 var isMatch = utils.memoize('isMatch', pattern, options, extglob.matcher); 129 return isMatch(str); 130 }; 131 132 /** 133 * Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but 134 * the pattern can match any part of the string. 135 * 136 * ```js 137 * var extglob = require('extglob'); 138 * console.log(extglob.contains('aa/bb/cc', '*b')); 139 * //=> true 140 * console.log(extglob.contains('aa/bb/cc', '*d')); 141 * //=> false 142 * ``` 143 * @param {String} `str` The string to match. 144 * @param {String} `pattern` Glob pattern to use for matching. 145 * @param {Object} `options` 146 * @return {Boolean} Returns true if the patter matches any part of `str`. 147 * @api public 148 */ 149 150 extglob.contains = function(str, pattern, options) { 151 if (typeof str !== 'string') { 152 throw new TypeError('expected a string'); 153 } 154 155 if (pattern === '' || pattern === ' ' || pattern === '.') { 156 return pattern === str; 157 } 158 159 var opts = extend({}, options, {contains: true}); 160 opts.strictClose = false; 161 opts.strictOpen = false; 162 return extglob.isMatch(str, pattern, opts); 163 }; 164 165 /** 166 * Takes an extglob pattern and returns a matcher function. The returned 167 * function takes the string to match as its only argument. 168 * 169 * ```js 170 * var extglob = require('extglob'); 171 * var isMatch = extglob.matcher('*.!(*a)'); 172 * 173 * console.log(isMatch('a.a')); 174 * //=> false 175 * console.log(isMatch('a.b')); 176 * //=> true 177 * ``` 178 * @param {String} `pattern` Extglob pattern 179 * @param {String} `options` 180 * @return {Boolean} 181 * @api public 182 */ 183 184 extglob.matcher = function(pattern, options) { 185 if (typeof pattern !== 'string') { 186 throw new TypeError('expected pattern to be a string'); 187 } 188 189 function matcher() { 190 var re = extglob.makeRe(pattern, options); 191 return function(str) { 192 return re.test(str); 193 }; 194 } 195 196 return utils.memoize('matcher', pattern, options, matcher); 197 }; 198 199 /** 200 * Convert the given `extglob` pattern into a regex-compatible string. Returns 201 * an object with the compiled result and the parsed AST. 202 * 203 * ```js 204 * var extglob = require('extglob'); 205 * console.log(extglob.create('*.!(*a)').output); 206 * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' 207 * ``` 208 * @param {String} `str` 209 * @param {Object} `options` 210 * @return {String} 211 * @api public 212 */ 213 214 extglob.create = function(pattern, options) { 215 if (typeof pattern !== 'string') { 216 throw new TypeError('expected pattern to be a string'); 217 } 218 219 function create() { 220 var ext = new Extglob(options); 221 var ast = ext.parse(pattern, options); 222 return ext.compile(ast, options); 223 } 224 225 return utils.memoize('create', pattern, options, create); 226 }; 227 228 /** 229 * Returns an array of matches captured by `pattern` in `string`, or `null` 230 * if the pattern did not match. 231 * 232 * ```js 233 * var extglob = require('extglob'); 234 * extglob.capture(pattern, string[, options]); 235 * 236 * console.log(extglob.capture('test/*.js', 'test/foo.js')); 237 * //=> ['foo'] 238 * console.log(extglob.capture('test/*.js', 'foo/bar.css')); 239 * //=> null 240 * ``` 241 * @param {String} `pattern` Glob pattern to use for matching. 242 * @param {String} `string` String to match 243 * @param {Object} `options` See available [options](#options) for changing how matches are performed 244 * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. 245 * @api public 246 */ 247 248 extglob.capture = function(pattern, str, options) { 249 var re = extglob.makeRe(pattern, extend({capture: true}, options)); 250 251 function match() { 252 return function(string) { 253 var match = re.exec(string); 254 if (!match) { 255 return null; 256 } 257 258 return match.slice(1); 259 }; 260 } 261 262 var capture = utils.memoize('capture', pattern, options, match); 263 return capture(str); 264 }; 265 266 /** 267 * Create a regular expression from the given `pattern` and `options`. 268 * 269 * ```js 270 * var extglob = require('extglob'); 271 * var re = extglob.makeRe('*.!(*a)'); 272 * console.log(re); 273 * //=> /^[^\/]*?\.(?![^\/]*?a)[^\/]*?$/ 274 * ``` 275 * @param {String} `pattern` The pattern to convert to regex. 276 * @param {Object} `options` 277 * @return {RegExp} 278 * @api public 279 */ 280 281 extglob.makeRe = function(pattern, options) { 282 if (pattern instanceof RegExp) { 283 return pattern; 284 } 285 286 if (typeof pattern !== 'string') { 287 throw new TypeError('expected pattern to be a string'); 288 } 289 290 if (pattern.length > MAX_LENGTH) { 291 throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); 292 } 293 294 function makeRe() { 295 var opts = extend({strictErrors: false}, options); 296 if (opts.strictErrors === true) opts.strict = true; 297 var res = extglob.create(pattern, opts); 298 return toRegex(res.output, opts); 299 } 300 301 var regex = utils.memoize('makeRe', pattern, options, makeRe); 302 if (regex.source.length > MAX_LENGTH) { 303 throw new SyntaxError('potentially malicious regex detected'); 304 } 305 306 return regex; 307 }; 308 309 /** 310 * Cache 311 */ 312 313 extglob.cache = utils.cache; 314 extglob.clearCache = function() { 315 extglob.cache.__data__ = {}; 316 }; 317 318 /** 319 * Expose `Extglob` constructor, parsers and compilers 320 */ 321 322 extglob.Extglob = Extglob; 323 extglob.compilers = compilers; 324 extglob.parsers = parsers; 325 326 /** 327 * Expose `extglob` 328 * @type {Function} 329 */ 330 331 module.exports = extglob;