blob: f0ba9177a3379499bd691cda4fa2021b17b0fa57 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2
3var utils = module.exports;
4var path = require('path');
5
6/**
7 * Module dependencies
8 */
9
10var Snapdragon = require('snapdragon');
11utils.define = require('define-property');
12utils.diff = require('arr-diff');
13utils.extend = require('extend-shallow');
14utils.pick = require('object.pick');
15utils.typeOf = require('kind-of');
16utils.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
25utils.isWindows = function() {
26 return path.sep === '\\' || process.platform === 'win32';
27};
28
29/**
30 * Get the `Snapdragon` instance to use
31 */
32
33utils.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
83utils.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
101utils.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
110utils.isString = function(val) {
111 return typeof val === 'string';
112};
113
114/**
115 * Return true if `val` is a non-empty string
116 */
117
118utils.isObject = function(val) {
119 return utils.typeOf(val) === 'object';
120};
121
122/**
123 * Returns true if the given `str` has special characters
124 */
125
126utils.hasSpecialChars = function(str) {
127 return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str);
128};
129
130/**
131 * Escape regex characters in the given string
132 */
133
134utils.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
145utils.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
156utils.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
166utils.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
182utils.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
194utils.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
205utils._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
214utils._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
226utils.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
248utils.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
270utils.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
281utils.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
295utils.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};