index.js
1 /*! 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 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'); 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 26 function 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 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); 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 131 function 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 145 function 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 163 function 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 182 function toNumber(val) { 183 return Number(val) || 0; 184 } 185 186 function isPadded(str) { 187 return /^-?0\d/.test(str); 188 } 189 190 function isValid(min, max) { 191 return (isValidNumber(min) || isValidLetter(min)) 192 && (isValidNumber(max) || isValidLetter(max)); 193 } 194 195 function isValidLetter(ch) { 196 return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch); 197 } 198 199 function isValidNumber(n) { 200 return isNumber(n) && !/\./.test(n); 201 } 202 203 /** 204 * Expose `fillRange` 205 * @type {Function} 206 */ 207 208 module.exports = fillRange;