utils.js 8.6 KB

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