utils.js
  1  'use strict';
  2  
  3  var utils = module.exports;
  4  var path = require('path');
  5  
  6  /**
  7   * Module dependencies
  8   */
  9  
 10  var Snapdragon = require('snapdragon');
 11  utils.define = require('define-property');
 12  utils.diff = require('arr-diff');
 13  utils.extend = require('extend-shallow');
 14  utils.pick = require('object.pick');
 15  utils.typeOf = require('kind-of');
 16  utils.unique = require('array-unique');
 17  
 18  /**
 19   * Returns true if the platform is windows, or `path.sep` is `\\`.
 20   * This is defined as a function to allow `path.sep` to be set in unit tests,
 21   * or by the user, if there is a reason to do so.
 22   * @return {Boolean}
 23   */
 24  
 25  utils.isWindows = function() {
 26    return path.sep === '\\' || process.platform === 'win32';
 27  };
 28  
 29  /**
 30   * Get the `Snapdragon` instance to use
 31   */
 32  
 33  utils.instantiate = function(ast, options) {
 34    var snapdragon;
 35    // if an instance was created by `.parse`, use that instance
 36    if (utils.typeOf(ast) === 'object' && ast.snapdragon) {
 37      snapdragon = ast.snapdragon;
 38    // if the user supplies an instance on options, use that instance
 39    } else if (utils.typeOf(options) === 'object' && options.snapdragon) {
 40      snapdragon = options.snapdragon;
 41    // create a new instance
 42    } else {
 43      snapdragon = new Snapdragon(options);
 44    }
 45  
 46    utils.define(snapdragon, 'parse', function(str, options) {
 47      var parsed = Snapdragon.prototype.parse.apply(this, arguments);
 48      parsed.input = str;
 49  
 50      // escape unmatched brace/bracket/parens
 51      var last = this.parser.stack.pop();
 52      if (last && this.options.strictErrors !== true) {
 53        var open = last.nodes[0];
 54        var inner = last.nodes[1];
 55        if (last.type === 'bracket') {
 56          if (inner.val.charAt(0) === '[') {
 57            inner.val = '\\' + inner.val;
 58          }
 59  
 60        } else {
 61          open.val = '\\' + open.val;
 62          var sibling = open.parent.nodes[1];
 63          if (sibling.type === 'star') {
 64            sibling.loose = true;
 65          }
 66        }
 67      }
 68  
 69      // add non-enumerable parser reference
 70      utils.define(parsed, 'parser', this.parser);
 71      return parsed;
 72    });
 73  
 74    return snapdragon;
 75  };
 76  
 77  /**
 78   * Create the key to use for memoization. The key is generated
 79   * by iterating over the options and concatenating key-value pairs
 80   * to the pattern string.
 81   */
 82  
 83  utils.createKey = function(pattern, options) {
 84    if (utils.typeOf(options) !== 'object') {
 85      return pattern;
 86    }
 87    var val = pattern;
 88    var keys = Object.keys(options);
 89    for (var i = 0; i < keys.length; i++) {
 90      var key = keys[i];
 91      val += ';' + key + '=' + String(options[key]);
 92    }
 93    return val;
 94  };
 95  
 96  /**
 97   * Cast `val` to an array
 98   * @return {Array}
 99   */
100  
101  utils.arrayify = function(val) {
102    if (typeof val === 'string') return [val];
103    return val ? (Array.isArray(val) ? val : [val]) : [];
104  };
105  
106  /**
107   * Return true if `val` is a non-empty string
108   */
109  
110  utils.isString = function(val) {
111    return typeof val === 'string';
112  };
113  
114  /**
115   * Return true if `val` is a non-empty string
116   */
117  
118  utils.isObject = function(val) {
119    return utils.typeOf(val) === 'object';
120  };
121  
122  /**
123   * Returns true if the given `str` has special characters
124   */
125  
126  utils.hasSpecialChars = function(str) {
127    return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str);
128  };
129  
130  /**
131   * Escape regex characters in the given string
132   */
133  
134  utils.escapeRegex = function(str) {
135    return str.replace(/[-[\]{}()^$|*+?.\\\/\s]/g, '\\$&');
136  };
137  
138  /**
139   * Normalize slashes in the given filepath.
140   *
141   * @param {String} `filepath`
142   * @return {String}
143   */
144  
145  utils.toPosixPath = function(str) {
146    return str.replace(/\\+/g, '/');
147  };
148  
149  /**
150   * Strip backslashes before special characters in a string.
151   *
152   * @param {String} `str`
153   * @return {String}
154   */
155  
156  utils.unescape = function(str) {
157    return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, ''));
158  };
159  
160  /**
161   * Strip the prefix from a filepath
162   * @param {String} `fp`
163   * @return {String}
164   */
165  
166  utils.stripPrefix = function(str) {
167    if (str.charAt(0) !== '.') {
168      return str;
169    }
170    var ch = str.charAt(1);
171    if (utils.isSlash(ch)) {
172      return str.slice(2);
173    }
174    return str;
175  };
176  
177  /**
178   * Returns true if the given str is an escaped or
179   * unescaped path character
180   */
181  
182  utils.isSlash = function(str) {
183    return str === '/' || str === '\\/' || str === '\\' || str === '\\\\';
184  };
185  
186  /**
187   * Returns a function that returns true if the given
188   * pattern matches or contains a `filepath`
189   *
190   * @param {String} `pattern`
191   * @return {Function}
192   */
193  
194  utils.matchPath = function(pattern, options) {
195    return (options && options.contains)
196      ? utils.containsPattern(pattern, options)
197      : utils.equalsPattern(pattern, options);
198  };
199  
200  /**
201   * Returns true if the given (original) filepath or unixified path are equal
202   * to the given pattern.
203   */
204  
205  utils._equals = function(filepath, unixPath, pattern) {
206    return pattern === filepath || pattern === unixPath;
207  };
208  
209  /**
210   * Returns true if the given (original) filepath or unixified path contain
211   * the given pattern.
212   */
213  
214  utils._contains = function(filepath, unixPath, pattern) {
215    return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1;
216  };
217  
218  /**
219   * Returns a function that returns true if the given
220   * pattern is the same as a given `filepath`
221   *
222   * @param {String} `pattern`
223   * @return {Function}
224   */
225  
226  utils.equalsPattern = function(pattern, options) {
227    var unixify = utils.unixify(options);
228    options = options || {};
229  
230    return function fn(filepath) {
231      var equal = utils._equals(filepath, unixify(filepath), pattern);
232      if (equal === true || options.nocase !== true) {
233        return equal;
234      }
235      var lower = filepath.toLowerCase();
236      return utils._equals(lower, unixify(lower), pattern);
237    };
238  };
239  
240  /**
241   * Returns a function that returns true if the given
242   * pattern contains a `filepath`
243   *
244   * @param {String} `pattern`
245   * @return {Function}
246   */
247  
248  utils.containsPattern = function(pattern, options) {
249    var unixify = utils.unixify(options);
250    options = options || {};
251  
252    return function(filepath) {
253      var contains = utils._contains(filepath, unixify(filepath), pattern);
254      if (contains === true || options.nocase !== true) {
255        return contains;
256      }
257      var lower = filepath.toLowerCase();
258      return utils._contains(lower, unixify(lower), pattern);
259    };
260  };
261  
262  /**
263   * Returns a function that returns true if the given
264   * regex matches the `filename` of a file path.
265   *
266   * @param {RegExp} `re` Matching regex
267   * @return {Function}
268   */
269  
270  utils.matchBasename = function(re) {
271    return function(filepath) {
272      return re.test(path.basename(filepath));
273    };
274  };
275  
276  /**
277   * Determines the filepath to return based on the provided options.
278   * @return {any}
279   */
280  
281  utils.value = function(str, unixify, options) {
282    if (options && options.unixify === false) {
283      return str;
284    }
285    return unixify(str);
286  };
287  
288  /**
289   * Returns a function that normalizes slashes in a string to forward
290   * slashes, strips `./` from beginning of paths, and optionally unescapes
291   * special characters.
292   * @return {Function}
293   */
294  
295  utils.unixify = function(options) {
296    options = options || {};
297    return function(filepath) {
298      if (utils.isWindows() || options.unixify === true) {
299        filepath = utils.toPosixPath(filepath);
300      }
301      if (options.stripPrefix !== false) {
302        filepath = utils.stripPrefix(filepath);
303      }
304      if (options.unescape === true) {
305        filepath = utils.unescape(filepath);
306      }
307      return filepath;
308    };
309  };