1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict';
|
9 |
|
10 | const util = require('util');
|
11 | const toRegexRange = require('to-regex-range');
|
12 |
|
13 | const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
|
14 |
|
15 | const transform = toNumber => {
|
16 | return value => toNumber === true ? Number(value) : String(value);
|
17 | };
|
18 |
|
19 | const isValidValue = value => {
|
20 | return typeof value === 'number' || (typeof value === 'string' && value !== '');
|
21 | };
|
22 |
|
23 | const isNumber = num => Number.isInteger(+num);
|
24 |
|
25 | const zeros = input => {
|
26 | let value = `${input}`;
|
27 | let index = -1;
|
28 | if (value[0] === '-') value = value.slice(1);
|
29 | if (value === '0') return false;
|
30 | while (value[++index] === '0');
|
31 | return index > 0;
|
32 | };
|
33 |
|
34 | const stringify = (start, end, options) => {
|
35 | if (typeof start === 'string' || typeof end === 'string') {
|
36 | return true;
|
37 | }
|
38 | return options.stringify === true;
|
39 | };
|
40 |
|
41 | const pad = (input, maxLength, toNumber) => {
|
42 | if (maxLength > 0) {
|
43 | let dash = input[0] === '-' ? '-' : '';
|
44 | if (dash) input = input.slice(1);
|
45 | input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0'));
|
46 | }
|
47 | if (toNumber === false) {
|
48 | return String(input);
|
49 | }
|
50 | return input;
|
51 | };
|
52 |
|
53 | const toMaxLen = (input, maxLength) => {
|
54 | let negative = input[0] === '-' ? '-' : '';
|
55 | if (negative) {
|
56 | input = input.slice(1);
|
57 | maxLength--;
|
58 | }
|
59 | while (input.length < maxLength) input = '0' + input;
|
60 | return negative ? ('-' + input) : input;
|
61 | };
|
62 |
|
63 | const toSequence = (parts, options) => {
|
64 | parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
65 | parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
66 |
|
67 | let prefix = options.capture ? '' : '?:';
|
68 | let positives = '';
|
69 | let negatives = '';
|
70 | let result;
|
71 |
|
72 | if (parts.positives.length) {
|
73 | positives = parts.positives.join('|');
|
74 | }
|
75 |
|
76 | if (parts.negatives.length) {
|
77 | negatives = `-(${prefix}${parts.negatives.join('|')})`;
|
78 | }
|
79 |
|
80 | if (positives && negatives) {
|
81 | result = `${positives}|${negatives}`;
|
82 | } else {
|
83 | result = positives || negatives;
|
84 | }
|
85 |
|
86 | if (options.wrap) {
|
87 | return `(${prefix}${result})`;
|
88 | }
|
89 |
|
90 | return result;
|
91 | };
|
92 |
|
93 | const toRange = (a, b, isNumbers, options) => {
|
94 | if (isNumbers) {
|
95 | return toRegexRange(a, b, { wrap: false, ...options });
|
96 | }
|
97 |
|
98 | let start = String.fromCharCode(a);
|
99 | if (a === b) return start;
|
100 |
|
101 | let stop = String.fromCharCode(b);
|
102 | return `[${start}-${stop}]`;
|
103 | };
|
104 |
|
105 | const toRegex = (start, end, options) => {
|
106 | if (Array.isArray(start)) {
|
107 | let wrap = options.wrap === true;
|
108 | let prefix = options.capture ? '' : '?:';
|
109 | return wrap ? `(${prefix}${start.join('|')})` : start.join('|');
|
110 | }
|
111 | return toRegexRange(start, end, options);
|
112 | };
|
113 |
|
114 | const rangeError = (...args) => {
|
115 | return new RangeError('Invalid range arguments: ' + util.inspect(...args));
|
116 | };
|
117 |
|
118 | const invalidRange = (start, end, options) => {
|
119 | if (options.strictRanges === true) throw rangeError([start, end]);
|
120 | return [];
|
121 | };
|
122 |
|
123 | const invalidStep = (step, options) => {
|
124 | if (options.strictRanges === true) {
|
125 | throw new TypeError(`Expected step "${step}" to be a number`);
|
126 | }
|
127 | return [];
|
128 | };
|
129 |
|
130 | const fillNumbers = (start, end, step = 1, options = {}) => {
|
131 | let a = Number(start);
|
132 | let b = Number(end);
|
133 |
|
134 | if (!Number.isInteger(a) || !Number.isInteger(b)) {
|
135 | if (options.strictRanges === true) throw rangeError([start, end]);
|
136 | return [];
|
137 | }
|
138 |
|
139 |
|
140 | if (a === 0) a = 0;
|
141 | if (b === 0) b = 0;
|
142 |
|
143 | let descending = a > b;
|
144 | let startString = String(start);
|
145 | let endString = String(end);
|
146 | let stepString = String(step);
|
147 | step = Math.max(Math.abs(step), 1);
|
148 |
|
149 | let padded = zeros(startString) || zeros(endString) || zeros(stepString);
|
150 | let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0;
|
151 | let toNumber = padded === false && stringify(start, end, options) === false;
|
152 | let format = options.transform || transform(toNumber);
|
153 |
|
154 | if (options.toRegex && step === 1) {
|
155 | return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options);
|
156 | }
|
157 |
|
158 | let parts = { negatives: [], positives: [] };
|
159 | let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num));
|
160 | let range = [];
|
161 | let index = 0;
|
162 |
|
163 | while (descending ? a >= b : a <= b) {
|
164 | if (options.toRegex === true && step > 1) {
|
165 | push(a);
|
166 | } else {
|
167 | range.push(pad(format(a, index), maxLen, toNumber));
|
168 | }
|
169 | a = descending ? a - step : a + step;
|
170 | index++;
|
171 | }
|
172 |
|
173 | if (options.toRegex === true) {
|
174 | return step > 1
|
175 | ? toSequence(parts, options)
|
176 | : toRegex(range, null, { wrap: false, ...options });
|
177 | }
|
178 |
|
179 | return range;
|
180 | };
|
181 |
|
182 | const fillLetters = (start, end, step = 1, options = {}) => {
|
183 | if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) {
|
184 | return invalidRange(start, end, options);
|
185 | }
|
186 |
|
187 |
|
188 | let format = options.transform || (val => String.fromCharCode(val));
|
189 | let a = `${start}`.charCodeAt(0);
|
190 | let b = `${end}`.charCodeAt(0);
|
191 |
|
192 | let descending = a > b;
|
193 | let min = Math.min(a, b);
|
194 | let max = Math.max(a, b);
|
195 |
|
196 | if (options.toRegex && step === 1) {
|
197 | return toRange(min, max, false, options);
|
198 | }
|
199 |
|
200 | let range = [];
|
201 | let index = 0;
|
202 |
|
203 | while (descending ? a >= b : a <= b) {
|
204 | range.push(format(a, index));
|
205 | a = descending ? a - step : a + step;
|
206 | index++;
|
207 | }
|
208 |
|
209 | if (options.toRegex === true) {
|
210 | return toRegex(range, null, { wrap: false, options });
|
211 | }
|
212 |
|
213 | return range;
|
214 | };
|
215 |
|
216 | const fill = (start, end, step, options = {}) => {
|
217 | if (end == null && isValidValue(start)) {
|
218 | return [start];
|
219 | }
|
220 |
|
221 | if (!isValidValue(start) || !isValidValue(end)) {
|
222 | return invalidRange(start, end, options);
|
223 | }
|
224 |
|
225 | if (typeof step === 'function') {
|
226 | return fill(start, end, 1, { transform: step });
|
227 | }
|
228 |
|
229 | if (isObject(step)) {
|
230 | return fill(start, end, 0, step);
|
231 | }
|
232 |
|
233 | let opts = { ...options };
|
234 | if (opts.capture === true) opts.wrap = true;
|
235 | step = step || opts.step || 1;
|
236 |
|
237 | if (!isNumber(step)) {
|
238 | if (step != null && !isObject(step)) return invalidStep(step, opts);
|
239 | return fill(start, end, 1, step);
|
240 | }
|
241 |
|
242 | if (isNumber(start) && isNumber(end)) {
|
243 | return fillNumbers(start, end, step, opts);
|
244 | }
|
245 |
|
246 | return fillLetters(start, end, Math.max(Math.abs(step), 1), opts);
|
247 | };
|
248 |
|
249 | module.exports = fill;
|