1 | /*!
|
---|
2 | * fill-range <https://github.com/jonschlinkert/fill-range>
|
---|
3 | *
|
---|
4 | * Copyright (c) 2014-present, Jon Schlinkert.
|
---|
5 | * Licensed under the MIT License.
|
---|
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 | // fix negative zero
|
---|
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;
|
---|