index.js 4.9 KB

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