blob: 294a2edde8f940b69c389a7fa1385600cf72206f [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001/*!
2 * fill-range <https://github.com/jonschlinkert/fill-range>
3 *
4 * Copyright (c) 2014-2015, 2017, Jon Schlinkert.
5 * Released under the MIT License.
6 */
7
8'use strict';
9
10var util = require('util');
11var isNumber = require('is-number');
12var extend = require('extend-shallow');
13var repeat = require('repeat-string');
14var toRegex = require('to-regex-range');
15
16/**
17 * Return a range of numbers or letters.
18 *
19 * @param {String} `start` Start of the range
20 * @param {String} `stop` End of the range
21 * @param {String} `step` Increment or decrement to use.
22 * @param {Function} `fn` Custom function to modify each element in the range.
23 * @return {Array}
24 */
25
26function fillRange(start, stop, step, options) {
27 if (typeof start === 'undefined') {
28 return [];
29 }
30
31 if (typeof stop === 'undefined' || start === stop) {
32 // special case, for handling negative zero
33 var isString = typeof start === 'string';
34 if (isNumber(start) && !toNumber(start)) {
35 return [isString ? '0' : 0];
36 }
37 return [start];
38 }
39
40 if (typeof step !== 'number' && typeof step !== 'string') {
41 options = step;
42 step = undefined;
43 }
44
45 if (typeof options === 'function') {
46 options = { transform: options };
47 }
48
49 var opts = extend({step: step}, options);
50 if (opts.step && !isValidNumber(opts.step)) {
51 if (opts.strictRanges === true) {
52 throw new TypeError('expected options.step to be a number');
53 }
54 return [];
55 }
56
57 opts.isNumber = isValidNumber(start) && isValidNumber(stop);
58 if (!opts.isNumber && !isValid(start, stop)) {
59 if (opts.strictRanges === true) {
60 throw new RangeError('invalid range arguments: ' + util.inspect([start, stop]));
61 }
62 return [];
63 }
64
65 opts.isPadded = isPadded(start) || isPadded(stop);
66 opts.toString = opts.stringify
67 || typeof opts.step === 'string'
68 || typeof start === 'string'
69 || typeof stop === 'string'
70 || !opts.isNumber;
71
72 if (opts.isPadded) {
73 opts.maxLength = Math.max(String(start).length, String(stop).length);
74 }
75
76 // support legacy minimatch/fill-range options
77 if (typeof opts.optimize === 'boolean') opts.toRegex = opts.optimize;
78 if (typeof opts.makeRe === 'boolean') opts.toRegex = opts.makeRe;
79 return expand(start, stop, opts);
80}
81
82function expand(start, stop, options) {
83 var a = options.isNumber ? toNumber(start) : start.charCodeAt(0);
84 var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0);
85
86 var step = Math.abs(toNumber(options.step)) || 1;
87 if (options.toRegex && step === 1) {
88 return toRange(a, b, start, stop, options);
89 }
90
91 var zero = {greater: [], lesser: []};
92 var asc = a < b;
93 var arr = new Array(Math.round((asc ? b - a : a - b) / step));
94 var idx = 0;
95
96 while (asc ? a <= b : a >= b) {
97 var val = options.isNumber ? a : String.fromCharCode(a);
98 if (options.toRegex && (val >= 0 || !options.isNumber)) {
99 zero.greater.push(val);
100 } else {
101 zero.lesser.push(Math.abs(val));
102 }
103
104 if (options.isPadded) {
105 val = zeros(val, options);
106 }
107
108 if (options.toString) {
109 val = String(val);
110 }
111
112 if (typeof options.transform === 'function') {
113 arr[idx++] = options.transform(val, a, b, step, idx, arr, options);
114 } else {
115 arr[idx++] = val;
116 }
117
118 if (asc) {
119 a += step;
120 } else {
121 a -= step;
122 }
123 }
124
125 if (options.toRegex === true) {
126 return toSequence(arr, zero, options);
127 }
128 return arr;
129}
130
131function toRange(a, b, start, stop, options) {
132 if (options.isPadded) {
133 return toRegex(start, stop, options);
134 }
135
136 if (options.isNumber) {
137 return toRegex(Math.min(a, b), Math.max(a, b), options);
138 }
139
140 var start = String.fromCharCode(Math.min(a, b));
141 var stop = String.fromCharCode(Math.max(a, b));
142 return '[' + start + '-' + stop + ']';
143}
144
145function toSequence(arr, zeros, options) {
146 var greater = '', lesser = '';
147 if (zeros.greater.length) {
148 greater = zeros.greater.join('|');
149 }
150 if (zeros.lesser.length) {
151 lesser = '-(' + zeros.lesser.join('|') + ')';
152 }
153 var res = greater && lesser
154 ? greater + '|' + lesser
155 : greater || lesser;
156
157 if (options.capture) {
158 return '(' + res + ')';
159 }
160 return res;
161}
162
163function zeros(val, options) {
164 if (options.isPadded) {
165 var str = String(val);
166 var len = str.length;
167 var dash = '';
168 if (str.charAt(0) === '-') {
169 dash = '-';
170 str = str.slice(1);
171 }
172 var diff = options.maxLength - len;
173 var pad = repeat('0', diff);
174 val = (dash + pad + str);
175 }
176 if (options.stringify) {
177 return String(val);
178 }
179 return val;
180}
181
182function toNumber(val) {
183 return Number(val) || 0;
184}
185
186function isPadded(str) {
187 return /^-?0\d/.test(str);
188}
189
190function isValid(min, max) {
191 return (isValidNumber(min) || isValidLetter(min))
192 && (isValidNumber(max) || isValidLetter(max));
193}
194
195function isValidLetter(ch) {
196 return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch);
197}
198
199function isValidNumber(n) {
200 return isNumber(n) && !/\./.test(n);
201}
202
203/**
204 * Expose `fillRange`
205 * @type {Function}
206 */
207
208module.exports = fillRange;