1
|
'use strict';
|
2
|
|
3
|
var splitString = require('split-string');
|
4
|
var utils = module.exports;
|
5
|
|
6
|
/**
|
7
|
* Module dependencies
|
8
|
*/
|
9
|
|
10
|
utils.extend = require('extend-shallow');
|
11
|
utils.flatten = require('arr-flatten');
|
12
|
utils.isObject = require('isobject');
|
13
|
utils.fillRange = require('fill-range');
|
14
|
utils.repeat = require('repeat-element');
|
15
|
utils.unique = require('array-unique');
|
16
|
|
17
|
utils.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
|
|
30
|
utils.isEmptySets = function(str) {
|
31
|
return /^(?:\{,\})+$/.test(str);
|
32
|
};
|
33
|
|
34
|
/**
|
35
|
* Returns true if the given string contains only empty brace sets.
|
36
|
*/
|
37
|
|
38
|
utils.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
|
|
52
|
utils.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
|
|
69
|
utils.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
|
|
87
|
utils.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
|
|
126
|
utils.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
|
|
145
|
utils.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
|
|
229
|
utils.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
|
|
294
|
utils.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
|
|
303
|
utils.stringifyArray = function(arr) {
|
304
|
return [utils.arrayify(arr).join('|')];
|
305
|
};
|
306
|
|
307
|
/**
|
308
|
* Cast `val` to an array.
|
309
|
* @param {*} `val`
|
310
|
*/
|
311
|
|
312
|
utils.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
|
|
327
|
utils.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
|
|
337
|
utils.last = function(arr, n) {
|
338
|
return arr[arr.length - (n || 1)];
|
339
|
};
|
340
|
|
341
|
utils.escapeRegex = function(str) {
|
342
|
return str.replace(/\\?([!^*?()[\]{}+?/])/g, '\\$1');
|
343
|
};
|