2 * fill-range <https://github.com/jonschlinkert/fill-range>
4 * Copyright (c) 2014-2015, 2017, Jon Schlinkert.
5 * Released under the MIT License.
10 var util = require('util');
11 var isNumber = require('is-number');
12 var extend = require('extend-shallow');
13 var repeat = require('repeat-string');
14 var toRegex = require('to-regex-range');
17 * Return a range of numbers or letters.
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.
26 function fillRange(start, stop, step, options) {
27 if (typeof start === 'undefined') {
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];
40 if (typeof step !== 'number' && typeof step !== 'string') {
45 if (typeof options === 'function') {
46 options = { transform: options };
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');
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]));
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'
73 opts.maxLength = Math.max(String(start).length, String(stop).length);
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);
82 function expand(start, stop, options) {
83 var a = options.isNumber ? toNumber(start) : start.charCodeAt(0);
84 var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0);
86 var step = Math.abs(toNumber(options.step)) || 1;
87 if (options.toRegex && step === 1) {
88 return toRange(a, b, start, stop, options);
91 var zero = {greater: [], lesser: []};
93 var arr = new Array(Math.round((asc ? b - a : a - b) / step));
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);
101 zero.lesser.push(Math.abs(val));
104 if (options.isPadded) {
105 val = zeros(val, options);
108 if (options.toString) {
112 if (typeof options.transform === 'function') {
113 arr[idx++] = options.transform(val, a, b, step, idx, arr, options);
125 if (options.toRegex === true) {
126 return toSequence(arr, zero, options);
131 function toRange(a, b, start, stop, options) {
132 if (options.isPadded) {
133 return toRegex(start, stop, options);
136 if (options.isNumber) {
137 return toRegex(Math.min(a, b), Math.max(a, b), options);
140 var start = String.fromCharCode(Math.min(a, b));
141 var stop = String.fromCharCode(Math.max(a, b));
142 return '[' + start + '-' + stop + ']';
145 function toSequence(arr, zeros, options) {
146 var greater = '', lesser = '';
147 if (zeros.greater.length) {
148 greater = zeros.greater.join('|');
150 if (zeros.lesser.length) {
151 lesser = '-(' + zeros.lesser.join('|') + ')';
153 var res = greater && lesser
154 ? greater + '|' + lesser
157 if (options.capture) {
158 return '(' + res + ')';
163 function zeros(val, options) {
164 if (options.isPadded) {
165 var str = String(val);
166 var len = str.length;
168 if (str.charAt(0) === '-') {
172 var diff = options.maxLength - len;
173 var pad = repeat('0', diff);
174 val = (dash + pad + str);
176 if (options.stringify) {
182 function toNumber(val) {
183 return Number(val) || 0;
186 function isPadded(str) {
187 return /^-?0\d/.test(str);
190 function isValid(min, max) {
191 return (isValidNumber(min) || isValidLetter(min))
192 && (isValidNumber(max) || isValidLetter(max));
195 function isValidLetter(ch) {
196 return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch);
199 function isValidNumber(n) {
200 return isNumber(n) && !/\./.test(n);
208 module.exports = fillRange;