You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. 'use strict';
  2. /**
  3. * Module dependencies
  4. */
  5. var util = require('util');
  6. var toRegex = require('to-regex');
  7. var extend = require('extend-shallow');
  8. /**
  9. * Local dependencies
  10. */
  11. var compilers = require('./lib/compilers');
  12. var parsers = require('./lib/parsers');
  13. var cache = require('./lib/cache');
  14. var utils = require('./lib/utils');
  15. var MAX_LENGTH = 1024 * 64;
  16. /**
  17. * The main function takes a list of strings and one or more
  18. * glob patterns to use for matching.
  19. *
  20. * ```js
  21. * var nm = require('nanomatch');
  22. * nm(list, patterns[, options]);
  23. *
  24. * console.log(nm(['a.js', 'a.txt'], ['*.js']));
  25. * //=> [ 'a.js' ]
  26. * ```
  27. * @param {Array} `list` A list of strings to match
  28. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  29. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  30. * @return {Array} Returns an array of matches
  31. * @summary false
  32. * @api public
  33. */
  34. function nanomatch(list, patterns, options) {
  35. patterns = utils.arrayify(patterns);
  36. list = utils.arrayify(list);
  37. var len = patterns.length;
  38. if (list.length === 0 || len === 0) {
  39. return [];
  40. }
  41. if (len === 1) {
  42. return nanomatch.match(list, patterns[0], options);
  43. }
  44. var negated = false;
  45. var omit = [];
  46. var keep = [];
  47. var idx = -1;
  48. while (++idx < len) {
  49. var pattern = patterns[idx];
  50. if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) {
  51. omit.push.apply(omit, nanomatch.match(list, pattern.slice(1), options));
  52. negated = true;
  53. } else {
  54. keep.push.apply(keep, nanomatch.match(list, pattern, options));
  55. }
  56. }
  57. // minimatch.match parity
  58. if (negated && keep.length === 0) {
  59. if (options && options.unixify === false) {
  60. keep = list.slice();
  61. } else {
  62. var unixify = utils.unixify(options);
  63. for (var i = 0; i < list.length; i++) {
  64. keep.push(unixify(list[i]));
  65. }
  66. }
  67. }
  68. var matches = utils.diff(keep, omit);
  69. if (!options || options.nodupes !== false) {
  70. return utils.unique(matches);
  71. }
  72. return matches;
  73. }
  74. /**
  75. * Similar to the main function, but `pattern` must be a string.
  76. *
  77. * ```js
  78. * var nm = require('nanomatch');
  79. * nm.match(list, pattern[, options]);
  80. *
  81. * console.log(nm.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a'));
  82. * //=> ['a.a', 'a.aa']
  83. * ```
  84. * @param {Array} `list` Array of strings to match
  85. * @param {String} `pattern` Glob pattern to use for matching.
  86. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  87. * @return {Array} Returns an array of matches
  88. * @api public
  89. */
  90. nanomatch.match = function(list, pattern, options) {
  91. if (Array.isArray(pattern)) {
  92. throw new TypeError('expected pattern to be a string');
  93. }
  94. var unixify = utils.unixify(options);
  95. var isMatch = memoize('match', pattern, options, nanomatch.matcher);
  96. var matches = [];
  97. list = utils.arrayify(list);
  98. var len = list.length;
  99. var idx = -1;
  100. while (++idx < len) {
  101. var ele = list[idx];
  102. if (ele === pattern || isMatch(ele)) {
  103. matches.push(utils.value(ele, unixify, options));
  104. }
  105. }
  106. // if no options were passed, uniquify results and return
  107. if (typeof options === 'undefined') {
  108. return utils.unique(matches);
  109. }
  110. if (matches.length === 0) {
  111. if (options.failglob === true) {
  112. throw new Error('no matches found for "' + pattern + '"');
  113. }
  114. if (options.nonull === true || options.nullglob === true) {
  115. return [options.unescape ? utils.unescape(pattern) : pattern];
  116. }
  117. }
  118. // if `opts.ignore` was defined, diff ignored list
  119. if (options.ignore) {
  120. matches = nanomatch.not(matches, options.ignore, options);
  121. }
  122. return options.nodupes !== false ? utils.unique(matches) : matches;
  123. };
  124. /**
  125. * Returns true if the specified `string` matches the given glob `pattern`.
  126. *
  127. * ```js
  128. * var nm = require('nanomatch');
  129. * nm.isMatch(string, pattern[, options]);
  130. *
  131. * console.log(nm.isMatch('a.a', '*.a'));
  132. * //=> true
  133. * console.log(nm.isMatch('a.b', '*.a'));
  134. * //=> false
  135. * ```
  136. * @param {String} `string` String to match
  137. * @param {String} `pattern` Glob pattern to use for matching.
  138. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  139. * @return {Boolean} Returns true if the string matches the glob pattern.
  140. * @api public
  141. */
  142. nanomatch.isMatch = function(str, pattern, options) {
  143. if (typeof str !== 'string') {
  144. throw new TypeError('expected a string: "' + util.inspect(str) + '"');
  145. }
  146. if (utils.isEmptyString(str) || utils.isEmptyString(pattern)) {
  147. return false;
  148. }
  149. var equals = utils.equalsPattern(options);
  150. if (equals(str)) {
  151. return true;
  152. }
  153. var isMatch = memoize('isMatch', pattern, options, nanomatch.matcher);
  154. return isMatch(str);
  155. };
  156. /**
  157. * Returns true if some of the elements in the given `list` match any of the
  158. * given glob `patterns`.
  159. *
  160. * ```js
  161. * var nm = require('nanomatch');
  162. * nm.some(list, patterns[, options]);
  163. *
  164. * console.log(nm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
  165. * // true
  166. * console.log(nm.some(['foo.js'], ['*.js', '!foo.js']));
  167. * // false
  168. * ```
  169. * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found.
  170. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  171. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  172. * @return {Boolean} Returns true if any patterns match `str`
  173. * @api public
  174. */
  175. nanomatch.some = function(list, patterns, options) {
  176. if (typeof list === 'string') {
  177. list = [list];
  178. }
  179. for (var i = 0; i < list.length; i++) {
  180. if (nanomatch(list[i], patterns, options).length === 1) {
  181. return true;
  182. }
  183. }
  184. return false;
  185. };
  186. /**
  187. * Returns true if every element in the given `list` matches
  188. * at least one of the given glob `patterns`.
  189. *
  190. * ```js
  191. * var nm = require('nanomatch');
  192. * nm.every(list, patterns[, options]);
  193. *
  194. * console.log(nm.every('foo.js', ['foo.js']));
  195. * // true
  196. * console.log(nm.every(['foo.js', 'bar.js'], ['*.js']));
  197. * // true
  198. * console.log(nm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
  199. * // false
  200. * console.log(nm.every(['foo.js'], ['*.js', '!foo.js']));
  201. * // false
  202. * ```
  203. * @param {String|Array} `list` The string or array of strings to test.
  204. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  205. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  206. * @return {Boolean} Returns true if any patterns match `str`
  207. * @api public
  208. */
  209. nanomatch.every = function(list, patterns, options) {
  210. if (typeof list === 'string') {
  211. list = [list];
  212. }
  213. for (var i = 0; i < list.length; i++) {
  214. if (nanomatch(list[i], patterns, options).length !== 1) {
  215. return false;
  216. }
  217. }
  218. return true;
  219. };
  220. /**
  221. * Returns true if **any** of the given glob `patterns`
  222. * match the specified `string`.
  223. *
  224. * ```js
  225. * var nm = require('nanomatch');
  226. * nm.any(string, patterns[, options]);
  227. *
  228. * console.log(nm.any('a.a', ['b.*', '*.a']));
  229. * //=> true
  230. * console.log(nm.any('a.a', 'b.*'));
  231. * //=> false
  232. * ```
  233. * @param {String|Array} `str` The string to test.
  234. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  235. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  236. * @return {Boolean} Returns true if any patterns match `str`
  237. * @api public
  238. */
  239. nanomatch.any = function(str, patterns, options) {
  240. if (typeof str !== 'string') {
  241. throw new TypeError('expected a string: "' + util.inspect(str) + '"');
  242. }
  243. if (utils.isEmptyString(str) || utils.isEmptyString(patterns)) {
  244. return false;
  245. }
  246. if (typeof patterns === 'string') {
  247. patterns = [patterns];
  248. }
  249. for (var i = 0; i < patterns.length; i++) {
  250. if (nanomatch.isMatch(str, patterns[i], options)) {
  251. return true;
  252. }
  253. }
  254. return false;
  255. };
  256. /**
  257. * Returns true if **all** of the given `patterns`
  258. * match the specified string.
  259. *
  260. * ```js
  261. * var nm = require('nanomatch');
  262. * nm.all(string, patterns[, options]);
  263. *
  264. * console.log(nm.all('foo.js', ['foo.js']));
  265. * // true
  266. *
  267. * console.log(nm.all('foo.js', ['*.js', '!foo.js']));
  268. * // false
  269. *
  270. * console.log(nm.all('foo.js', ['*.js', 'foo.js']));
  271. * // true
  272. *
  273. * console.log(nm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js']));
  274. * // true
  275. * ```
  276. * @param {String|Array} `str` The string to test.
  277. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  278. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  279. * @return {Boolean} Returns true if any patterns match `str`
  280. * @api public
  281. */
  282. nanomatch.all = function(str, patterns, options) {
  283. if (typeof str !== 'string') {
  284. throw new TypeError('expected a string: "' + util.inspect(str) + '"');
  285. }
  286. if (typeof patterns === 'string') {
  287. patterns = [patterns];
  288. }
  289. for (var i = 0; i < patterns.length; i++) {
  290. if (!nanomatch.isMatch(str, patterns[i], options)) {
  291. return false;
  292. }
  293. }
  294. return true;
  295. };
  296. /**
  297. * Returns a list of strings that _**do not match any**_ of the given `patterns`.
  298. *
  299. * ```js
  300. * var nm = require('nanomatch');
  301. * nm.not(list, patterns[, options]);
  302. *
  303. * console.log(nm.not(['a.a', 'b.b', 'c.c'], '*.a'));
  304. * //=> ['b.b', 'c.c']
  305. * ```
  306. * @param {Array} `list` Array of strings to match.
  307. * @param {String|Array} `patterns` One or more glob pattern to use for matching.
  308. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  309. * @return {Array} Returns an array of strings that **do not match** the given patterns.
  310. * @api public
  311. */
  312. nanomatch.not = function(list, patterns, options) {
  313. var opts = extend({}, options);
  314. var ignore = opts.ignore;
  315. delete opts.ignore;
  316. list = utils.arrayify(list);
  317. var matches = utils.diff(list, nanomatch(list, patterns, opts));
  318. if (ignore) {
  319. matches = utils.diff(matches, nanomatch(list, ignore));
  320. }
  321. return opts.nodupes !== false ? utils.unique(matches) : matches;
  322. };
  323. /**
  324. * Returns true if the given `string` contains the given pattern. Similar
  325. * to [.isMatch](#isMatch) but the pattern can match any part of the string.
  326. *
  327. * ```js
  328. * var nm = require('nanomatch');
  329. * nm.contains(string, pattern[, options]);
  330. *
  331. * console.log(nm.contains('aa/bb/cc', '*b'));
  332. * //=> true
  333. * console.log(nm.contains('aa/bb/cc', '*d'));
  334. * //=> false
  335. * ```
  336. * @param {String} `str` The string to match.
  337. * @param {String|Array} `patterns` Glob pattern to use for matching.
  338. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  339. * @return {Boolean} Returns true if the patter matches any part of `str`.
  340. * @api public
  341. */
  342. nanomatch.contains = function(str, patterns, options) {
  343. if (typeof str !== 'string') {
  344. throw new TypeError('expected a string: "' + util.inspect(str) + '"');
  345. }
  346. if (typeof patterns === 'string') {
  347. if (utils.isEmptyString(str) || utils.isEmptyString(patterns)) {
  348. return false;
  349. }
  350. var equals = utils.equalsPattern(patterns, options);
  351. if (equals(str)) {
  352. return true;
  353. }
  354. var contains = utils.containsPattern(patterns, options);
  355. if (contains(str)) {
  356. return true;
  357. }
  358. }
  359. var opts = extend({}, options, {contains: true});
  360. return nanomatch.any(str, patterns, opts);
  361. };
  362. /**
  363. * Returns true if the given pattern and options should enable
  364. * the `matchBase` option.
  365. * @return {Boolean}
  366. * @api private
  367. */
  368. nanomatch.matchBase = function(pattern, options) {
  369. if (pattern && pattern.indexOf('/') !== -1 || !options) return false;
  370. return options.basename === true || options.matchBase === true;
  371. };
  372. /**
  373. * Filter the keys of the given object with the given `glob` pattern
  374. * and `options`. Does not attempt to match nested keys. If you need this feature,
  375. * use [glob-object][] instead.
  376. *
  377. * ```js
  378. * var nm = require('nanomatch');
  379. * nm.matchKeys(object, patterns[, options]);
  380. *
  381. * var obj = { aa: 'a', ab: 'b', ac: 'c' };
  382. * console.log(nm.matchKeys(obj, '*b'));
  383. * //=> { ab: 'b' }
  384. * ```
  385. * @param {Object} `object` The object with keys to filter.
  386. * @param {String|Array} `patterns` One or more glob patterns to use for matching.
  387. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  388. * @return {Object} Returns an object with only keys that match the given patterns.
  389. * @api public
  390. */
  391. nanomatch.matchKeys = function(obj, patterns, options) {
  392. if (!utils.isObject(obj)) {
  393. throw new TypeError('expected the first argument to be an object');
  394. }
  395. var keys = nanomatch(Object.keys(obj), patterns, options);
  396. return utils.pick(obj, keys);
  397. };
  398. /**
  399. * Returns a memoized matcher function from the given glob `pattern` and `options`.
  400. * The returned function takes a string to match as its only argument and returns
  401. * true if the string is a match.
  402. *
  403. * ```js
  404. * var nm = require('nanomatch');
  405. * nm.matcher(pattern[, options]);
  406. *
  407. * var isMatch = nm.matcher('*.!(*a)');
  408. * console.log(isMatch('a.a'));
  409. * //=> false
  410. * console.log(isMatch('a.b'));
  411. * //=> true
  412. * ```
  413. * @param {String} `pattern` Glob pattern
  414. * @param {Object} `options` See available [options](#options) for changing how matches are performed.
  415. * @return {Function} Returns a matcher function.
  416. * @api public
  417. */
  418. nanomatch.matcher = function matcher(pattern, options) {
  419. if (utils.isEmptyString(pattern)) {
  420. return function() {
  421. return false;
  422. };
  423. }
  424. if (Array.isArray(pattern)) {
  425. return compose(pattern, options, matcher);
  426. }
  427. // if pattern is a regex
  428. if (pattern instanceof RegExp) {
  429. return test(pattern);
  430. }
  431. // if pattern is invalid
  432. if (!utils.isString(pattern)) {
  433. throw new TypeError('expected pattern to be an array, string or regex');
  434. }
  435. // if pattern is a non-glob string
  436. if (!utils.hasSpecialChars(pattern)) {
  437. if (options && options.nocase === true) {
  438. pattern = pattern.toLowerCase();
  439. }
  440. return utils.matchPath(pattern, options);
  441. }
  442. // if pattern is a glob string
  443. var re = nanomatch.makeRe(pattern, options);
  444. // if `options.matchBase` or `options.basename` is defined
  445. if (nanomatch.matchBase(pattern, options)) {
  446. return utils.matchBasename(re, options);
  447. }
  448. function test(regex) {
  449. var equals = utils.equalsPattern(options);
  450. var unixify = utils.unixify(options);
  451. return function(str) {
  452. if (equals(str)) {
  453. return true;
  454. }
  455. if (regex.test(unixify(str))) {
  456. return true;
  457. }
  458. return false;
  459. };
  460. }
  461. // create matcher function
  462. var matcherFn = test(re);
  463. // set result object from compiler on matcher function,
  464. // as a non-enumerable property. useful for debugging
  465. utils.define(matcherFn, 'result', re.result);
  466. return matcherFn;
  467. };
  468. /**
  469. * Returns an array of matches captured by `pattern` in `string, or
  470. * `null` if the pattern did not match.
  471. *
  472. * ```js
  473. * var nm = require('nanomatch');
  474. * nm.capture(pattern, string[, options]);
  475. *
  476. * console.log(nm.capture('test/*.js', 'test/foo.js'));
  477. * //=> ['foo']
  478. * console.log(nm.capture('test/*.js', 'foo/bar.css'));
  479. * //=> null
  480. * ```
  481. * @param {String} `pattern` Glob pattern to use for matching.
  482. * @param {String} `string` String to match
  483. * @param {Object} `options` See available [options](#options) for changing how matches are performed
  484. * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`.
  485. * @api public
  486. */
  487. nanomatch.capture = function(pattern, str, options) {
  488. var re = nanomatch.makeRe(pattern, extend({capture: true}, options));
  489. var unixify = utils.unixify(options);
  490. function match() {
  491. return function(string) {
  492. var match = re.exec(unixify(string));
  493. if (!match) {
  494. return null;
  495. }
  496. return match.slice(1);
  497. };
  498. }
  499. var capture = memoize('capture', pattern, options, match);
  500. return capture(str);
  501. };
  502. /**
  503. * Create a regular expression from the given glob `pattern`.
  504. *
  505. * ```js
  506. * var nm = require('nanomatch');
  507. * nm.makeRe(pattern[, options]);
  508. *
  509. * console.log(nm.makeRe('*.js'));
  510. * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/
  511. * ```
  512. * @param {String} `pattern` A glob pattern to convert to regex.
  513. * @param {Object} `options` See available [options](#options) for changing how matches are performed.
  514. * @return {RegExp} Returns a regex created from the given pattern.
  515. * @api public
  516. */
  517. nanomatch.makeRe = function(pattern, options) {
  518. if (pattern instanceof RegExp) {
  519. return pattern;
  520. }
  521. if (typeof pattern !== 'string') {
  522. throw new TypeError('expected pattern to be a string');
  523. }
  524. if (pattern.length > MAX_LENGTH) {
  525. throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters');
  526. }
  527. function makeRe() {
  528. var opts = utils.extend({wrap: false}, options);
  529. var result = nanomatch.create(pattern, opts);
  530. var regex = toRegex(result.output, opts);
  531. utils.define(regex, 'result', result);
  532. return regex;
  533. }
  534. return memoize('makeRe', pattern, options, makeRe);
  535. };
  536. /**
  537. * Parses the given glob `pattern` and returns an object with the compiled `output`
  538. * and optional source `map`.
  539. *
  540. * ```js
  541. * var nm = require('nanomatch');
  542. * nm.create(pattern[, options]);
  543. *
  544. * console.log(nm.create('abc/*.js'));
  545. * // { options: { source: 'string', sourcemap: true },
  546. * // state: {},
  547. * // compilers:
  548. * // { ... },
  549. * // output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js',
  550. * // ast:
  551. * // { type: 'root',
  552. * // errors: [],
  553. * // nodes:
  554. * // [ ... ],
  555. * // dot: false,
  556. * // input: 'abc/*.js' },
  557. * // parsingErrors: [],
  558. * // map:
  559. * // { version: 3,
  560. * // sources: [ 'string' ],
  561. * // names: [],
  562. * // mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE',
  563. * // sourcesContent: [ 'abc/*.js' ] },
  564. * // position: { line: 1, column: 28 },
  565. * // content: {},
  566. * // files: {},
  567. * // idx: 6 }
  568. * ```
  569. * @param {String} `pattern` Glob pattern to parse and compile.
  570. * @param {Object} `options` Any [options](#options) to change how parsing and compiling is performed.
  571. * @return {Object} Returns an object with the parsed AST, compiled string and optional source map.
  572. * @api public
  573. */
  574. nanomatch.create = function(pattern, options) {
  575. if (typeof pattern !== 'string') {
  576. throw new TypeError('expected a string');
  577. }
  578. function create() {
  579. return nanomatch.compile(nanomatch.parse(pattern, options), options);
  580. }
  581. return memoize('create', pattern, options, create);
  582. };
  583. /**
  584. * Parse the given `str` with the given `options`.
  585. *
  586. * ```js
  587. * var nm = require('nanomatch');
  588. * nm.parse(pattern[, options]);
  589. *
  590. * var ast = nm.parse('a/{b,c}/d');
  591. * console.log(ast);
  592. * // { type: 'root',
  593. * // errors: [],
  594. * // input: 'a/{b,c}/d',
  595. * // nodes:
  596. * // [ { type: 'bos', val: '' },
  597. * // { type: 'text', val: 'a/' },
  598. * // { type: 'brace',
  599. * // nodes:
  600. * // [ { type: 'brace.open', val: '{' },
  601. * // { type: 'text', val: 'b,c' },
  602. * // { type: 'brace.close', val: '}' } ] },
  603. * // { type: 'text', val: '/d' },
  604. * // { type: 'eos', val: '' } ] }
  605. * ```
  606. * @param {String} `str`
  607. * @param {Object} `options`
  608. * @return {Object} Returns an AST
  609. * @api public
  610. */
  611. nanomatch.parse = function(pattern, options) {
  612. if (typeof pattern !== 'string') {
  613. throw new TypeError('expected a string');
  614. }
  615. function parse() {
  616. var snapdragon = utils.instantiate(null, options);
  617. parsers(snapdragon, options);
  618. var ast = snapdragon.parse(pattern, options);
  619. utils.define(ast, 'snapdragon', snapdragon);
  620. ast.input = pattern;
  621. return ast;
  622. }
  623. return memoize('parse', pattern, options, parse);
  624. };
  625. /**
  626. * Compile the given `ast` or string with the given `options`.
  627. *
  628. * ```js
  629. * var nm = require('nanomatch');
  630. * nm.compile(ast[, options]);
  631. *
  632. * var ast = nm.parse('a/{b,c}/d');
  633. * console.log(nm.compile(ast));
  634. * // { options: { source: 'string' },
  635. * // state: {},
  636. * // compilers:
  637. * // { eos: [Function],
  638. * // noop: [Function],
  639. * // bos: [Function],
  640. * // brace: [Function],
  641. * // 'brace.open': [Function],
  642. * // text: [Function],
  643. * // 'brace.close': [Function] },
  644. * // output: [ 'a/(b|c)/d' ],
  645. * // ast:
  646. * // { ... },
  647. * // parsingErrors: [] }
  648. * ```
  649. * @param {Object|String} `ast`
  650. * @param {Object} `options`
  651. * @return {Object} Returns an object that has an `output` property with the compiled string.
  652. * @api public
  653. */
  654. nanomatch.compile = function(ast, options) {
  655. if (typeof ast === 'string') {
  656. ast = nanomatch.parse(ast, options);
  657. }
  658. function compile() {
  659. var snapdragon = utils.instantiate(ast, options);
  660. compilers(snapdragon, options);
  661. return snapdragon.compile(ast, options);
  662. }
  663. return memoize('compile', ast.input, options, compile);
  664. };
  665. /**
  666. * Clear the regex cache.
  667. *
  668. * ```js
  669. * nm.clearCache();
  670. * ```
  671. * @api public
  672. */
  673. nanomatch.clearCache = function() {
  674. nanomatch.cache.__data__ = {};
  675. };
  676. /**
  677. * Compose a matcher function with the given patterns.
  678. * This allows matcher functions to be compiled once and
  679. * called multiple times.
  680. */
  681. function compose(patterns, options, matcher) {
  682. var matchers;
  683. return memoize('compose', String(patterns), options, function() {
  684. return function(file) {
  685. // delay composition until it's invoked the first time,
  686. // after that it won't be called again
  687. if (!matchers) {
  688. matchers = [];
  689. for (var i = 0; i < patterns.length; i++) {
  690. matchers.push(matcher(patterns[i], options));
  691. }
  692. }
  693. var len = matchers.length;
  694. while (len--) {
  695. if (matchers[len](file) === true) {
  696. return true;
  697. }
  698. }
  699. return false;
  700. };
  701. });
  702. }
  703. /**
  704. * Memoize a generated regex or function. A unique key is generated
  705. * from the `type` (usually method name), the `pattern`, and
  706. * user-defined options.
  707. */
  708. function memoize(type, pattern, options, fn) {
  709. var key = utils.createKey(type + '=' + pattern, options);
  710. if (options && options.cache === false) {
  711. return fn(pattern, options);
  712. }
  713. if (cache.has(type, key)) {
  714. return cache.get(type, key);
  715. }
  716. var val = fn(pattern, options);
  717. cache.set(type, key, val);
  718. return val;
  719. }
  720. /**
  721. * Expose compiler, parser and cache on `nanomatch`
  722. */
  723. nanomatch.compilers = compilers;
  724. nanomatch.parsers = parsers;
  725. nanomatch.cache = cache;
  726. /**
  727. * Expose `nanomatch`
  728. * @type {Function}
  729. */
  730. module.exports = nanomatch;