UNPKG

6.32 kBJavaScriptView Raw
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
10const util = require('util');
11const toRegexRange = require('to-regex-range');
12
13const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
14
15const transform = toNumber => {
16 return value => toNumber === true ? Number(value) : String(value);
17};
18
19const isValidValue = value => {
20 return typeof value === 'number' || (typeof value === 'string' && value !== '');
21};
22
23const isNumber = num => Number.isInteger(+num);
24
25const 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
34const stringify = (start, end, options) => {
35 if (typeof start === 'string' || typeof end === 'string') {
36 return true;
37 }
38 return options.stringify === true;
39};
40
41const 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
53const 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
63const 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
93const 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
105const 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
114const rangeError = (...args) => {
115 return new RangeError('Invalid range arguments: ' + util.inspect(...args));
116};
117
118const invalidRange = (start, end, options) => {
119 if (options.strictRanges === true) throw rangeError([start, end]);
120 return [];
121};
122
123const invalidStep = (step, options) => {
124 if (options.strictRanges === true) {
125 throw new TypeError(`Expected step "${step}" to be a number`);
126 }
127 return [];
128};
129
130const 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
182const 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
216const 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
249module.exports = fill;