index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*!
  2. * fill-range <https://github.com/jonschlinkert/fill-range>
  3. *
  4. * Copyright (c) 2014-present, Jon Schlinkert.
  5. * Licensed under the MIT License.
  6. */
  7. 'use strict';
  8. const util = require('util');
  9. const toRegexRange = require('to-regex-range');
  10. const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
  11. const transform = toNumber => {
  12. return value => toNumber === true ? Number(value) : String(value);
  13. };
  14. const isValidValue = value => {
  15. return typeof value === 'number' || (typeof value === 'string' && value !== '');
  16. };
  17. const isNumber = num => Number.isInteger(+num);
  18. const zeros = input => {
  19. let value = `${input}`;
  20. let index = -1;
  21. if (value[0] === '-') value = value.slice(1);
  22. if (value === '0') return false;
  23. while (value[++index] === '0');
  24. return index > 0;
  25. };
  26. const stringify = (start, end, options) => {
  27. if (typeof start === 'string' || typeof end === 'string') {
  28. return true;
  29. }
  30. return options.stringify === true;
  31. };
  32. const pad = (input, maxLength, toNumber) => {
  33. if (maxLength > 0) {
  34. let dash = input[0] === '-' ? '-' : '';
  35. if (dash) input = input.slice(1);
  36. input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0'));
  37. }
  38. if (toNumber === false) {
  39. return String(input);
  40. }
  41. return input;
  42. };
  43. const toMaxLen = (input, maxLength) => {
  44. let negative = input[0] === '-' ? '-' : '';
  45. if (negative) {
  46. input = input.slice(1);
  47. maxLength--;
  48. }
  49. while (input.length < maxLength) input = '0' + input;
  50. return negative ? ('-' + input) : input;
  51. };
  52. const toSequence = (parts, options) => {
  53. parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
  54. parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
  55. let prefix = options.capture ? '' : '?:';
  56. let positives = '';
  57. let negatives = '';
  58. let result;
  59. if (parts.positives.length) {
  60. positives = parts.positives.join('|');
  61. }
  62. if (parts.negatives.length) {
  63. negatives = `-(${prefix}${parts.negatives.join('|')})`;
  64. }
  65. if (positives && negatives) {
  66. result = `${positives}|${negatives}`;
  67. } else {
  68. result = positives || negatives;
  69. }
  70. if (options.wrap) {
  71. return `(${prefix}${result})`;
  72. }
  73. return result;
  74. };
  75. const toRange = (a, b, isNumbers, options) => {
  76. if (isNumbers) {
  77. return toRegexRange(a, b, { wrap: false, ...options });
  78. }
  79. let start = String.fromCharCode(a);
  80. if (a === b) return start;
  81. let stop = String.fromCharCode(b);
  82. return `[${start}-${stop}]`;
  83. };
  84. const toRegex = (start, end, options) => {
  85. if (Array.isArray(start)) {
  86. let wrap = options.wrap === true;
  87. let prefix = options.capture ? '' : '?:';
  88. return wrap ? `(${prefix}${start.join('|')})` : start.join('|');
  89. }
  90. return toRegexRange(start, end, options);
  91. };
  92. const rangeError = (...args) => {
  93. return new RangeError('Invalid range arguments: ' + util.inspect(...args));
  94. };
  95. const invalidRange = (start, end, options) => {
  96. if (options.strictRanges === true) throw rangeError([start, end]);
  97. return [];
  98. };
  99. const invalidStep = (step, options) => {
  100. if (options.strictRanges === true) {
  101. throw new TypeError(`Expected step "${step}" to be a number`);
  102. }
  103. return [];
  104. };
  105. const fillNumbers = (start, end, step = 1, options = {}) => {
  106. let a = Number(start);
  107. let b = Number(end);
  108. if (!Number.isInteger(a) || !Number.isInteger(b)) {
  109. if (options.strictRanges === true) throw rangeError([start, end]);
  110. return [];
  111. }
  112. // fix negative zero
  113. if (a === 0) a = 0;
  114. if (b === 0) b = 0;
  115. let descending = a > b;
  116. let startString = String(start);
  117. let endString = String(end);
  118. let stepString = String(step);
  119. step = Math.max(Math.abs(step), 1);
  120. let padded = zeros(startString) || zeros(endString) || zeros(stepString);
  121. let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0;
  122. let toNumber = padded === false && stringify(start, end, options) === false;
  123. let format = options.transform || transform(toNumber);
  124. if (options.toRegex && step === 1) {
  125. return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options);
  126. }
  127. let parts = { negatives: [], positives: [] };
  128. let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num));
  129. let range = [];
  130. let index = 0;
  131. while (descending ? a >= b : a <= b) {
  132. if (options.toRegex === true && step > 1) {
  133. push(a);
  134. } else {
  135. range.push(pad(format(a, index), maxLen, toNumber));
  136. }
  137. a = descending ? a - step : a + step;
  138. index++;
  139. }
  140. if (options.toRegex === true) {
  141. return step > 1
  142. ? toSequence(parts, options)
  143. : toRegex(range, null, { wrap: false, ...options });
  144. }
  145. return range;
  146. };
  147. const fillLetters = (start, end, step = 1, options = {}) => {
  148. if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) {
  149. return invalidRange(start, end, options);
  150. }
  151. let format = options.transform || (val => String.fromCharCode(val));
  152. let a = `${start}`.charCodeAt(0);
  153. let b = `${end}`.charCodeAt(0);
  154. let descending = a > b;
  155. let min = Math.min(a, b);
  156. let max = Math.max(a, b);
  157. if (options.toRegex && step === 1) {
  158. return toRange(min, max, false, options);
  159. }
  160. let range = [];
  161. let index = 0;
  162. while (descending ? a >= b : a <= b) {
  163. range.push(format(a, index));
  164. a = descending ? a - step : a + step;
  165. index++;
  166. }
  167. if (options.toRegex === true) {
  168. return toRegex(range, null, { wrap: false, options });
  169. }
  170. return range;
  171. };
  172. const fill = (start, end, step, options = {}) => {
  173. if (end == null && isValidValue(start)) {
  174. return [start];
  175. }
  176. if (!isValidValue(start) || !isValidValue(end)) {
  177. return invalidRange(start, end, options);
  178. }
  179. if (typeof step === 'function') {
  180. return fill(start, end, 1, { transform: step });
  181. }
  182. if (isObject(step)) {
  183. return fill(start, end, 0, step);
  184. }
  185. let opts = { ...options };
  186. if (opts.capture === true) opts.wrap = true;
  187. step = step || opts.step || 1;
  188. if (!isNumber(step)) {
  189. if (step != null && !isObject(step)) return invalidStep(step, opts);
  190. return fill(start, end, 1, step);
  191. }
  192. if (isNumber(start) && isNumber(end)) {
  193. return fillNumbers(start, end, step, opts);
  194. }
  195. return fillLetters(start, end, Math.max(Math.abs(step), 1), opts);
  196. };
  197. module.exports = fill;