2 * to-regex-range <https://github.com/jonschlinkert/to-regex-range>
4 * Copyright (c) 2015, 2017, Jon Schlinkert.
5 * Released under the MIT License.
10 var repeat = require('repeat-string');
11 var isNumber = require('is-number');
14 function toRegexRange(min, max, options) {
15 if (isNumber(min) === false) {
16 throw new RangeError('toRegexRange: first argument is invalid.');
19 if (typeof max === 'undefined' || min === max) {
23 if (isNumber(max) === false) {
24 throw new RangeError('toRegexRange: second argument is invalid.');
27 options = options || {};
28 var relax = String(options.relaxZeros);
29 var shorthand = String(options.shorthand);
30 var capture = String(options.capture);
31 var key = min + ':' + max + '=' + relax + shorthand + capture;
32 if (cache.hasOwnProperty(key)) {
33 return cache[key].result;
36 var a = Math.min(min, max);
37 var b = Math.max(min, max);
39 if (Math.abs(a - b) === 1) {
40 var result = min + '|' + max;
41 if (options.capture) {
42 return '(' + result + ')';
47 var isPadded = padding(min) || padding(max);
51 var tok = {min: min, max: max, a: a, b: b};
53 tok.isPadded = isPadded;
54 tok.maxLen = String(tok.max).length;
58 var newMin = b < 0 ? Math.abs(b) : 1;
59 var newMax = Math.abs(a);
60 negatives = splitToPatterns(newMin, newMax, tok, options);
65 positives = splitToPatterns(a, b, tok, options);
68 tok.negatives = negatives;
69 tok.positives = positives;
70 tok.result = siftPatterns(negatives, positives, options);
72 if (options.capture && (positives.length + negatives.length) > 1) {
73 tok.result = '(' + tok.result + ')';
80 function siftPatterns(neg, pos, options) {
81 var onlyNegative = filterPatterns(neg, pos, '-', false, options) || [];
82 var onlyPositive = filterPatterns(pos, neg, '', false, options) || [];
83 var intersected = filterPatterns(neg, pos, '-?', true, options) || [];
84 var subpatterns = onlyNegative.concat(intersected).concat(onlyPositive);
85 return subpatterns.join('|');
88 function splitToRanges(min, max) {
94 var stop = +countNines(min, nines);
96 while (min <= stop && stop <= max) {
97 stops = push(stops, stop);
99 stop = +countNines(min, nines);
103 stop = countZeros(max + 1, zeros) - 1;
105 while (min < stop && stop <= max) {
106 stops = push(stops, stop);
108 stop = countZeros(max + 1, zeros) - 1;
116 * Convert a range to a regex pattern
117 * @param {Number} `start`
118 * @param {Number} `stop`
122 function rangeToPattern(start, stop, options) {
123 if (start === stop) {
124 return {pattern: String(start), digits: []};
127 var zipped = zip(String(start), String(stop));
128 var len = zipped.length, i = -1;
134 var numbers = zipped[i];
135 var startDigit = numbers[0];
136 var stopDigit = numbers[1];
138 if (startDigit === stopDigit) {
139 pattern += startDigit;
141 } else if (startDigit !== '0' || stopDigit !== '9') {
142 pattern += toCharacterClass(startDigit, stopDigit);
150 pattern += options.shorthand ? '\\d' : '[0-9]';
153 return { pattern: pattern, digits: [digits] };
156 function splitToPatterns(min, max, tok, options) {
157 var ranges = splitToRanges(min, max);
158 var len = ranges.length;
165 while (++idx < len) {
166 var range = ranges[idx];
167 var obj = rangeToPattern(start, range, options);
170 if (!tok.isPadded && prev && prev.pattern === obj.pattern) {
171 if (prev.digits.length > 1) {
174 prev.digits.push(obj.digits[0]);
175 prev.string = prev.pattern + toQuantifier(prev.digits);
181 zeros = padZeros(range, tok);
184 obj.string = zeros + obj.pattern + toQuantifier(obj.digits);
193 function filterPatterns(arr, comparison, prefix, intersection, options) {
196 for (var i = 0; i < arr.length; i++) {
198 var ele = tok.string;
200 if (options.relaxZeros !== false) {
201 if (prefix === '-' && ele.charAt(0) === '0') {
202 if (ele.charAt(1) === '{') {
203 ele = '0*' + ele.replace(/^0\{\d+\}/, '');
205 ele = '0*' + ele.slice(1);
210 if (!intersection && !contains(comparison, 'string', ele)) {
211 res.push(prefix + ele);
214 if (intersection && contains(comparison, 'string', ele)) {
215 res.push(prefix + ele);
222 * Zip strings (`for in` can be used on string characters)
227 for (var ch in a) arr.push([a[ch], b[ch]]);
231 function compare(a, b) {
232 return a > b ? 1 : b > a ? -1 : 0;
235 function push(arr, ele) {
236 if (arr.indexOf(ele) === -1) arr.push(ele);
240 function contains(arr, key, val) {
241 for (var i = 0; i < arr.length; i++) {
242 if (arr[i][key] === val) {
249 function countNines(min, len) {
250 return String(min).slice(0, -len) + repeat('9', len);
253 function countZeros(integer, zeros) {
254 return integer - (integer % Math.pow(10, zeros));
257 function toQuantifier(digits) {
258 var start = digits[0];
259 var stop = digits[1] ? (',' + digits[1]) : '';
260 if (!stop && (!start || start === 1)) {
263 return '{' + start + stop + '}';
266 function toCharacterClass(a, b) {
267 return '[' + a + ((b - a === 1) ? '' : '-') + b + ']';
270 function padding(str) {
271 return /^-?(0+)\d/.exec(str);
274 function padZeros(val, tok) {
276 var diff = Math.abs(tok.maxLen - String(val).length);
283 return '0{' + diff + '}';
291 * Expose `toRegexRange`
294 module.exports = toRegexRange;