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