| /*! |
| * fill-range <https://github.com/jonschlinkert/fill-range> |
| * |
| * Copyright (c) 2014-2015, 2017, Jon Schlinkert. |
| * Released under the MIT License. |
| */ |
| |
| 'use strict'; |
| |
| var util = require('util'); |
| var isNumber = require('is-number'); |
| var extend = require('extend-shallow'); |
| var repeat = require('repeat-string'); |
| var toRegex = require('to-regex-range'); |
| |
| /** |
| * Return a range of numbers or letters. |
| * |
| * @param {String} `start` Start of the range |
| * @param {String} `stop` End of the range |
| * @param {String} `step` Increment or decrement to use. |
| * @param {Function} `fn` Custom function to modify each element in the range. |
| * @return {Array} |
| */ |
| |
| function fillRange(start, stop, step, options) { |
| if (typeof start === 'undefined') { |
| return []; |
| } |
| |
| if (typeof stop === 'undefined' || start === stop) { |
| // special case, for handling negative zero |
| var isString = typeof start === 'string'; |
| if (isNumber(start) && !toNumber(start)) { |
| return [isString ? '0' : 0]; |
| } |
| return [start]; |
| } |
| |
| if (typeof step !== 'number' && typeof step !== 'string') { |
| options = step; |
| step = undefined; |
| } |
| |
| if (typeof options === 'function') { |
| options = { transform: options }; |
| } |
| |
| var opts = extend({step: step}, options); |
| if (opts.step && !isValidNumber(opts.step)) { |
| if (opts.strictRanges === true) { |
| throw new TypeError('expected options.step to be a number'); |
| } |
| return []; |
| } |
| |
| opts.isNumber = isValidNumber(start) && isValidNumber(stop); |
| if (!opts.isNumber && !isValid(start, stop)) { |
| if (opts.strictRanges === true) { |
| throw new RangeError('invalid range arguments: ' + util.inspect([start, stop])); |
| } |
| return []; |
| } |
| |
| opts.isPadded = isPadded(start) || isPadded(stop); |
| opts.toString = opts.stringify |
| || typeof opts.step === 'string' |
| || typeof start === 'string' |
| || typeof stop === 'string' |
| || !opts.isNumber; |
| |
| if (opts.isPadded) { |
| opts.maxLength = Math.max(String(start).length, String(stop).length); |
| } |
| |
| // support legacy minimatch/fill-range options |
| if (typeof opts.optimize === 'boolean') opts.toRegex = opts.optimize; |
| if (typeof opts.makeRe === 'boolean') opts.toRegex = opts.makeRe; |
| return expand(start, stop, opts); |
| } |
| |
| function expand(start, stop, options) { |
| var a = options.isNumber ? toNumber(start) : start.charCodeAt(0); |
| var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0); |
| |
| var step = Math.abs(toNumber(options.step)) || 1; |
| if (options.toRegex && step === 1) { |
| return toRange(a, b, start, stop, options); |
| } |
| |
| var zero = {greater: [], lesser: []}; |
| var asc = a < b; |
| var arr = new Array(Math.round((asc ? b - a : a - b) / step)); |
| var idx = 0; |
| |
| while (asc ? a <= b : a >= b) { |
| var val = options.isNumber ? a : String.fromCharCode(a); |
| if (options.toRegex && (val >= 0 || !options.isNumber)) { |
| zero.greater.push(val); |
| } else { |
| zero.lesser.push(Math.abs(val)); |
| } |
| |
| if (options.isPadded) { |
| val = zeros(val, options); |
| } |
| |
| if (options.toString) { |
| val = String(val); |
| } |
| |
| if (typeof options.transform === 'function') { |
| arr[idx++] = options.transform(val, a, b, step, idx, arr, options); |
| } else { |
| arr[idx++] = val; |
| } |
| |
| if (asc) { |
| a += step; |
| } else { |
| a -= step; |
| } |
| } |
| |
| if (options.toRegex === true) { |
| return toSequence(arr, zero, options); |
| } |
| return arr; |
| } |
| |
| function toRange(a, b, start, stop, options) { |
| if (options.isPadded) { |
| return toRegex(start, stop, options); |
| } |
| |
| if (options.isNumber) { |
| return toRegex(Math.min(a, b), Math.max(a, b), options); |
| } |
| |
| var start = String.fromCharCode(Math.min(a, b)); |
| var stop = String.fromCharCode(Math.max(a, b)); |
| return '[' + start + '-' + stop + ']'; |
| } |
| |
| function toSequence(arr, zeros, options) { |
| var greater = '', lesser = ''; |
| if (zeros.greater.length) { |
| greater = zeros.greater.join('|'); |
| } |
| if (zeros.lesser.length) { |
| lesser = '-(' + zeros.lesser.join('|') + ')'; |
| } |
| var res = greater && lesser |
| ? greater + '|' + lesser |
| : greater || lesser; |
| |
| if (options.capture) { |
| return '(' + res + ')'; |
| } |
| return res; |
| } |
| |
| function zeros(val, options) { |
| if (options.isPadded) { |
| var str = String(val); |
| var len = str.length; |
| var dash = ''; |
| if (str.charAt(0) === '-') { |
| dash = '-'; |
| str = str.slice(1); |
| } |
| var diff = options.maxLength - len; |
| var pad = repeat('0', diff); |
| val = (dash + pad + str); |
| } |
| if (options.stringify) { |
| return String(val); |
| } |
| return val; |
| } |
| |
| function toNumber(val) { |
| return Number(val) || 0; |
| } |
| |
| function isPadded(str) { |
| return /^-?0\d/.test(str); |
| } |
| |
| function isValid(min, max) { |
| return (isValidNumber(min) || isValidLetter(min)) |
| && (isValidNumber(max) || isValidLetter(max)); |
| } |
| |
| function isValidLetter(ch) { |
| return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch); |
| } |
| |
| function isValidNumber(n) { |
| return isNumber(n) && !/\./.test(n); |
| } |
| |
| /** |
| * Expose `fillRange` |
| * @type {Function} |
| */ |
| |
| module.exports = fillRange; |