250 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			250 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /*! | ||
|  |  * fill-range <https://github.com/jonschlinkert/fill-range>
 | ||
|  |  * | ||
|  |  * Copyright (c) 2014-present, Jon Schlinkert. | ||
|  |  * Licensed under the MIT License. | ||
|  |  */ | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | const util = require('util'); | ||
|  | const toRegexRange = require('to-regex-range'); | ||
|  | 
 | ||
|  | const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); | ||
|  | 
 | ||
|  | const transform = toNumber => { | ||
|  |   return value => toNumber === true ? Number(value) : String(value); | ||
|  | }; | ||
|  | 
 | ||
|  | const isValidValue = value => { | ||
|  |   return typeof value === 'number' || (typeof value === 'string' && value !== ''); | ||
|  | }; | ||
|  | 
 | ||
|  | const isNumber = num => Number.isInteger(+num); | ||
|  | 
 | ||
|  | const zeros = input => { | ||
|  |   let value = `${input}`; | ||
|  |   let index = -1; | ||
|  |   if (value[0] === '-') value = value.slice(1); | ||
|  |   if (value === '0') return false; | ||
|  |   while (value[++index] === '0'); | ||
|  |   return index > 0; | ||
|  | }; | ||
|  | 
 | ||
|  | const stringify = (start, end, options) => { | ||
|  |   if (typeof start === 'string' || typeof end === 'string') { | ||
|  |     return true; | ||
|  |   } | ||
|  |   return options.stringify === true; | ||
|  | }; | ||
|  | 
 | ||
|  | const pad = (input, maxLength, toNumber) => { | ||
|  |   if (maxLength > 0) { | ||
|  |     let dash = input[0] === '-' ? '-' : ''; | ||
|  |     if (dash) input = input.slice(1); | ||
|  |     input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); | ||
|  |   } | ||
|  |   if (toNumber === false) { | ||
|  |     return String(input); | ||
|  |   } | ||
|  |   return input; | ||
|  | }; | ||
|  | 
 | ||
|  | const toMaxLen = (input, maxLength) => { | ||
|  |   let negative = input[0] === '-' ? '-' : ''; | ||
|  |   if (negative) { | ||
|  |     input = input.slice(1); | ||
|  |     maxLength--; | ||
|  |   } | ||
|  |   while (input.length < maxLength) input = '0' + input; | ||
|  |   return negative ? ('-' + input) : input; | ||
|  | }; | ||
|  | 
 | ||
|  | const toSequence = (parts, options) => { | ||
|  |   parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); | ||
|  |   parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); | ||
|  | 
 | ||
|  |   let prefix = options.capture ? '' : '?:'; | ||
|  |   let positives = ''; | ||
|  |   let negatives = ''; | ||
|  |   let result; | ||
|  | 
 | ||
|  |   if (parts.positives.length) { | ||
|  |     positives = parts.positives.join('|'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (parts.negatives.length) { | ||
|  |     negatives = `-(${prefix}${parts.negatives.join('|')})`; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (positives && negatives) { | ||
|  |     result = `${positives}|${negatives}`; | ||
|  |   } else { | ||
|  |     result = positives || negatives; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (options.wrap) { | ||
|  |     return `(${prefix}${result})`; | ||
|  |   } | ||
|  | 
 | ||
|  |   return result; | ||
|  | }; | ||
|  | 
 | ||
|  | const toRange = (a, b, isNumbers, options) => { | ||
|  |   if (isNumbers) { | ||
|  |     return toRegexRange(a, b, { wrap: false, ...options }); | ||
|  |   } | ||
|  | 
 | ||
|  |   let start = String.fromCharCode(a); | ||
|  |   if (a === b) return start; | ||
|  | 
 | ||
|  |   let stop = String.fromCharCode(b); | ||
|  |   return `[${start}-${stop}]`; | ||
|  | }; | ||
|  | 
 | ||
|  | const toRegex = (start, end, options) => { | ||
|  |   if (Array.isArray(start)) { | ||
|  |     let wrap = options.wrap === true; | ||
|  |     let prefix = options.capture ? '' : '?:'; | ||
|  |     return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); | ||
|  |   } | ||
|  |   return toRegexRange(start, end, options); | ||
|  | }; | ||
|  | 
 | ||
|  | const rangeError = (...args) => { | ||
|  |   return new RangeError('Invalid range arguments: ' + util.inspect(...args)); | ||
|  | }; | ||
|  | 
 | ||
|  | const invalidRange = (start, end, options) => { | ||
|  |   if (options.strictRanges === true) throw rangeError([start, end]); | ||
|  |   return []; | ||
|  | }; | ||
|  | 
 | ||
|  | const invalidStep = (step, options) => { | ||
|  |   if (options.strictRanges === true) { | ||
|  |     throw new TypeError(`Expected step "${step}" to be a number`); | ||
|  |   } | ||
|  |   return []; | ||
|  | }; | ||
|  | 
 | ||
|  | const fillNumbers = (start, end, step = 1, options = {}) => { | ||
|  |   let a = Number(start); | ||
|  |   let b = Number(end); | ||
|  | 
 | ||
|  |   if (!Number.isInteger(a) || !Number.isInteger(b)) { | ||
|  |     if (options.strictRanges === true) throw rangeError([start, end]); | ||
|  |     return []; | ||
|  |   } | ||
|  | 
 | ||
|  |   // fix negative zero
 | ||
|  |   if (a === 0) a = 0; | ||
|  |   if (b === 0) b = 0; | ||
|  | 
 | ||
|  |   let descending = a > b; | ||
|  |   let startString = String(start); | ||
|  |   let endString = String(end); | ||
|  |   let stepString = String(step); | ||
|  |   step = Math.max(Math.abs(step), 1); | ||
|  | 
 | ||
|  |   let padded = zeros(startString) || zeros(endString) || zeros(stepString); | ||
|  |   let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; | ||
|  |   let toNumber = padded === false && stringify(start, end, options) === false; | ||
|  |   let format = options.transform || transform(toNumber); | ||
|  | 
 | ||
|  |   if (options.toRegex && step === 1) { | ||
|  |     return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   let parts = { negatives: [], positives: [] }; | ||
|  |   let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); | ||
|  |   let range = []; | ||
|  |   let index = 0; | ||
|  | 
 | ||
|  |   while (descending ? a >= b : a <= b) { | ||
|  |     if (options.toRegex === true && step > 1) { | ||
|  |       push(a); | ||
|  |     } else { | ||
|  |       range.push(pad(format(a, index), maxLen, toNumber)); | ||
|  |     } | ||
|  |     a = descending ? a - step : a + step; | ||
|  |     index++; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (options.toRegex === true) { | ||
|  |     return step > 1 | ||
|  |       ? toSequence(parts, options) | ||
|  |       : toRegex(range, null, { wrap: false, ...options }); | ||
|  |   } | ||
|  | 
 | ||
|  |   return range; | ||
|  | }; | ||
|  | 
 | ||
|  | const fillLetters = (start, end, step = 1, options = {}) => { | ||
|  |   if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { | ||
|  |     return invalidRange(start, end, options); | ||
|  |   } | ||
|  | 
 | ||
|  | 
 | ||
|  |   let format = options.transform || (val => String.fromCharCode(val)); | ||
|  |   let a = `${start}`.charCodeAt(0); | ||
|  |   let b = `${end}`.charCodeAt(0); | ||
|  | 
 | ||
|  |   let descending = a > b; | ||
|  |   let min = Math.min(a, b); | ||
|  |   let max = Math.max(a, b); | ||
|  | 
 | ||
|  |   if (options.toRegex && step === 1) { | ||
|  |     return toRange(min, max, false, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   let range = []; | ||
|  |   let index = 0; | ||
|  | 
 | ||
|  |   while (descending ? a >= b : a <= b) { | ||
|  |     range.push(format(a, index)); | ||
|  |     a = descending ? a - step : a + step; | ||
|  |     index++; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (options.toRegex === true) { | ||
|  |     return toRegex(range, null, { wrap: false, options }); | ||
|  |   } | ||
|  | 
 | ||
|  |   return range; | ||
|  | }; | ||
|  | 
 | ||
|  | const fill = (start, end, step, options = {}) => { | ||
|  |   if (end == null && isValidValue(start)) { | ||
|  |     return [start]; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!isValidValue(start) || !isValidValue(end)) { | ||
|  |     return invalidRange(start, end, options); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (typeof step === 'function') { | ||
|  |     return fill(start, end, 1, { transform: step }); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (isObject(step)) { | ||
|  |     return fill(start, end, 0, step); | ||
|  |   } | ||
|  | 
 | ||
|  |   let opts = { ...options }; | ||
|  |   if (opts.capture === true) opts.wrap = true; | ||
|  |   step = step || opts.step || 1; | ||
|  | 
 | ||
|  |   if (!isNumber(step)) { | ||
|  |     if (step != null && !isObject(step)) return invalidStep(step, opts); | ||
|  |     return fill(start, end, 1, step); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (isNumber(start) && isNumber(end)) { | ||
|  |     return fillNumbers(start, end, step, opts); | ||
|  |   } | ||
|  | 
 | ||
|  |   return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); | ||
|  | }; | ||
|  | 
 | ||
|  | module.exports = fill; |