blob: 471667171d3241dd2d7e402dfaada1eed6cee759 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2
3var splitString = require('split-string');
4var utils = module.exports;
5
6/**
7 * Module dependencies
8 */
9
10utils.extend = require('extend-shallow');
11utils.flatten = require('arr-flatten');
12utils.isObject = require('isobject');
13utils.fillRange = require('fill-range');
14utils.repeat = require('repeat-element');
15utils.unique = require('array-unique');
16
17utils.define = function(obj, key, val) {
18 Object.defineProperty(obj, key, {
19 writable: true,
20 configurable: true,
21 enumerable: false,
22 value: val
23 });
24};
25
26/**
27 * Returns true if the given string contains only empty brace sets.
28 */
29
30utils.isEmptySets = function(str) {
31 return /^(?:\{,\})+$/.test(str);
32};
33
34/**
35 * Returns true if the given string contains only empty brace sets.
36 */
37
38utils.isQuotedString = function(str) {
39 var open = str.charAt(0);
40 if (open === '\'' || open === '"' || open === '`') {
41 return str.slice(-1) === open;
42 }
43 return false;
44};
45
46/**
47 * Create the key to use for memoization. The unique key is generated
48 * by iterating over the options and concatenating key-value pairs
49 * to the pattern string.
50 */
51
52utils.createKey = function(pattern, options) {
53 var id = pattern;
54 if (typeof options === 'undefined') {
55 return id;
56 }
57 var keys = Object.keys(options);
58 for (var i = 0; i < keys.length; i++) {
59 var key = keys[i];
60 id += ';' + key + '=' + String(options[key]);
61 }
62 return id;
63};
64
65/**
66 * Normalize options
67 */
68
69utils.createOptions = function(options) {
70 var opts = utils.extend.apply(null, arguments);
71 if (typeof opts.expand === 'boolean') {
72 opts.optimize = !opts.expand;
73 }
74 if (typeof opts.optimize === 'boolean') {
75 opts.expand = !opts.optimize;
76 }
77 if (opts.optimize === true) {
78 opts.makeRe = true;
79 }
80 return opts;
81};
82
83/**
84 * Join patterns in `a` to patterns in `b`
85 */
86
87utils.join = function(a, b, options) {
88 options = options || {};
89 a = utils.arrayify(a);
90 b = utils.arrayify(b);
91
92 if (!a.length) return b;
93 if (!b.length) return a;
94
95 var len = a.length;
96 var idx = -1;
97 var arr = [];
98
99 while (++idx < len) {
100 var val = a[idx];
101 if (Array.isArray(val)) {
102 for (var i = 0; i < val.length; i++) {
103 val[i] = utils.join(val[i], b, options);
104 }
105 arr.push(val);
106 continue;
107 }
108
109 for (var j = 0; j < b.length; j++) {
110 var bval = b[j];
111
112 if (Array.isArray(bval)) {
113 arr.push(utils.join(val, bval, options));
114 } else {
115 arr.push(val + bval);
116 }
117 }
118 }
119 return arr;
120};
121
122/**
123 * Split the given string on `,` if not escaped.
124 */
125
126utils.split = function(str, options) {
127 var opts = utils.extend({sep: ','}, options);
128 if (typeof opts.keepQuotes !== 'boolean') {
129 opts.keepQuotes = true;
130 }
131 if (opts.unescape === false) {
132 opts.keepEscaping = true;
133 }
134 return splitString(str, opts, utils.escapeBrackets(opts));
135};
136
137/**
138 * Expand ranges or sets in the given `pattern`.
139 *
140 * @param {String} `str`
141 * @param {Object} `options`
142 * @return {Object}
143 */
144
145utils.expand = function(str, options) {
146 var opts = utils.extend({rangeLimit: 10000}, options);
147 var segs = utils.split(str, opts);
148 var tok = { segs: segs };
149
150 if (utils.isQuotedString(str)) {
151 return tok;
152 }
153
154 if (opts.rangeLimit === true) {
155 opts.rangeLimit = 10000;
156 }
157
158 if (segs.length > 1) {
159 if (opts.optimize === false) {
160 tok.val = segs[0];
161 return tok;
162 }
163
164 tok.segs = utils.stringifyArray(tok.segs);
165 } else if (segs.length === 1) {
166 var arr = str.split('..');
167
168 if (arr.length === 1) {
169 tok.val = tok.segs[tok.segs.length - 1] || tok.val || str;
170 tok.segs = [];
171 return tok;
172 }
173
174 if (arr.length === 2 && arr[0] === arr[1]) {
175 tok.escaped = true;
176 tok.val = arr[0];
177 tok.segs = [];
178 return tok;
179 }
180
181 if (arr.length > 1) {
182 if (opts.optimize !== false) {
183 opts.optimize = true;
184 delete opts.expand;
185 }
186
187 if (opts.optimize !== true) {
188 var min = Math.min(arr[0], arr[1]);
189 var max = Math.max(arr[0], arr[1]);
190 var step = arr[2] || 1;
191
192 if (opts.rangeLimit !== false && ((max - min) / step >= opts.rangeLimit)) {
193 throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
194 }
195 }
196
197 arr.push(opts);
198 tok.segs = utils.fillRange.apply(null, arr);
199
200 if (!tok.segs.length) {
201 tok.escaped = true;
202 tok.val = str;
203 return tok;
204 }
205
206 if (opts.optimize === true) {
207 tok.segs = utils.stringifyArray(tok.segs);
208 }
209
210 if (tok.segs === '') {
211 tok.val = str;
212 } else {
213 tok.val = tok.segs[0];
214 }
215 return tok;
216 }
217 } else {
218 tok.val = str;
219 }
220 return tok;
221};
222
223/**
224 * Ensure commas inside brackets and parens are not split.
225 * @param {Object} `tok` Token from the `split-string` module
226 * @return {undefined}
227 */
228
229utils.escapeBrackets = function(options) {
230 return function(tok) {
231 if (tok.escaped && tok.val === 'b') {
232 tok.val = '\\b';
233 return;
234 }
235
236 if (tok.val !== '(' && tok.val !== '[') return;
237 var opts = utils.extend({}, options);
238 var brackets = [];
239 var parens = [];
240 var stack = [];
241 var val = tok.val;
242 var str = tok.str;
243 var i = tok.idx - 1;
244
245 while (++i < str.length) {
246 var ch = str[i];
247
248 if (ch === '\\') {
249 val += (opts.keepEscaping === false ? '' : ch) + str[++i];
250 continue;
251 }
252
253 if (ch === '(') {
254 parens.push(ch);
255 stack.push(ch);
256 }
257
258 if (ch === '[') {
259 brackets.push(ch);
260 stack.push(ch);
261 }
262
263 if (ch === ')') {
264 parens.pop();
265 stack.pop();
266 if (!stack.length) {
267 val += ch;
268 break;
269 }
270 }
271
272 if (ch === ']') {
273 brackets.pop();
274 stack.pop();
275 if (!stack.length) {
276 val += ch;
277 break;
278 }
279 }
280 val += ch;
281 }
282
283 tok.split = false;
284 tok.val = val.slice(1);
285 tok.idx = i;
286 };
287};
288
289/**
290 * Returns true if the given string looks like a regex quantifier
291 * @return {Boolean}
292 */
293
294utils.isQuantifier = function(str) {
295 return /^(?:[0-9]?,[0-9]|[0-9],)$/.test(str);
296};
297
298/**
299 * Cast `val` to an array.
300 * @param {*} `val`
301 */
302
303utils.stringifyArray = function(arr) {
304 return [utils.arrayify(arr).join('|')];
305};
306
307/**
308 * Cast `val` to an array.
309 * @param {*} `val`
310 */
311
312utils.arrayify = function(arr) {
313 if (typeof arr === 'undefined') {
314 return [];
315 }
316 if (typeof arr === 'string') {
317 return [arr];
318 }
319 return arr;
320};
321
322/**
323 * Returns true if the given `str` is a non-empty string
324 * @return {Boolean}
325 */
326
327utils.isString = function(str) {
328 return str != null && typeof str === 'string';
329};
330
331/**
332 * Get the last element from `array`
333 * @param {Array} `array`
334 * @return {*}
335 */
336
337utils.last = function(arr, n) {
338 return arr[arr.length - (n || 1)];
339};
340
341utils.escapeRegex = function(str) {
342 return str.replace(/\\?([!^*?()[\]{}+?/])/g, '\\$1');
343};