UNPKG

4.99 kBJavaScriptView Raw
1/*!
2 * to-regex-range <https://github.com/jonschlinkert/to-regex-range>
3 *
4 * Copyright (c) 2015, Jon Schlinkert.
5 * Licensed under the MIT License.
6 */
7
8'use strict';
9
10var repeat = require('repeat-string');
11var isNumber = require('is-number');
12var cache = {range: {}, rangeToPattern: {}};
13
14function toRegexRange(min, max) {
15 if (isNumber(min) === false) {
16 throw new RangeError('toRegexRange: first argument is invalid.');
17 }
18
19 if (typeof max === 'undefined') {
20 return '' + min;
21 }
22
23 if (isNumber(max) === false) {
24 throw new RangeError('toRegexRange: second argument is invalid.');
25 }
26
27 var a = Math.min(min, max);
28 var b = Math.max(min, max);
29
30 if (a === b) return String(a);
31
32 var key = min + ':' + max;
33 if (cache.range.hasOwnProperty(key)) {
34 return cache.range[key];
35 }
36
37 var positives = [];
38 var negatives = [];
39
40 if (a < 0) {
41 var newMin = b < 0 ? Math.abs(b) : 1;
42 var newMax = Math.abs(a);
43 negatives = splitToPatterns(newMin, newMax);
44 a = 0;
45 }
46
47 if (b >= 0) {
48 positives = splitToPatterns(a, b);
49 }
50
51 var str = siftPatterns(negatives, positives);
52 cache.range[key] = str;
53 return str;
54}
55
56function siftPatterns(negatives, positives) {
57 var onlyNegative = filterPatterns(negatives, positives, '-');
58 var onlyPositive = filterPatterns(positives, negatives, '');
59 var intersected = filterPatterns(negatives, positives, '-?', true);
60 var subpatterns = onlyNegative.concat(intersected || []).concat(onlyPositive || []);
61 return subpatterns.join('|');
62}
63
64function splitToRanges(min, max) {
65 min = Number(min);
66 max = Number(max);
67
68 var nines = 1;
69 var stops = [max];
70 var stop = +countNines(min, nines);
71
72 while (min <= stop && stop <= max) {
73 stops = push(stops, stop);
74 nines += 1;
75 stop = +countNines(min, nines);
76 }
77
78 var zeros = 1;
79 stop = countZeros(max + 1, zeros) - 1;
80
81 while (min < stop && stop <= max) {
82 stops = push(stops, stop);
83 zeros += 1;
84 stop = countZeros(max + 1, zeros) - 1;
85 }
86
87 stops.sort(compare);
88 return stops;
89}
90
91/**
92 * Convert a range to a regex pattern
93 * @param {Number} `start`
94 * @param {Number} `stop`
95 * @return {String}
96 */
97
98function rangeToPattern(start, stop) {
99 if (start === stop) {
100 return {pattern: String(start), digits: []};
101 }
102
103 var key = start + ':' + stop;
104 if (cache.rangeToPattern.hasOwnProperty(key)) {
105 return cache.rangeToPattern[key];
106 }
107
108 var zipped = zip(String(start), String(stop));
109 var len = zipped.length, i = -1;
110
111 var pattern = '';
112 var digits = 0;
113
114 while (++i < len) {
115 var range = zipped[i];
116 var startDigit = range[0];
117 var stopDigit = range[1];
118
119 if (startDigit === stopDigit) {
120 pattern += startDigit;
121
122 } else if (startDigit !== '0' || stopDigit !== '9') {
123 pattern += toRange(startDigit, stopDigit);
124
125 } else {
126 digits += 1;
127 }
128 }
129
130 if (digits) {
131 pattern += '[0-9]';
132 }
133
134 return { pattern: pattern, digits: [digits] };
135}
136
137/**
138 * Zip strings (`for in` can be used on string characters)
139 */
140
141function zip(a, b) {
142 var arr = [];
143 for (var ch in a) arr.push([a[ch], b[ch]]);
144 return arr;
145}
146
147function splitToPatterns(min, max) {
148 var ranges = splitToRanges(min, max);
149 var len = ranges.length;
150 var idx = -1;
151
152 var start = min;
153 var tokens = [];
154 var prev;
155
156 while (++idx < len) {
157 var range = ranges[idx];
158 var tok = rangeToPattern(start, range);
159
160 if (prev && prev.pattern === tok.pattern) {
161 if (prev.digits.length > 1) {
162 prev.digits.pop();
163 }
164 prev.digits.push(tok.digits[0]);
165 prev.string = prev.pattern + toQuantifier(prev.digits);
166 start = range + 1;
167 continue;
168 }
169
170 tok.string = tok.pattern + toQuantifier(tok.digits);
171 tokens.push(tok);
172 start = range + 1;
173 prev = tok;
174 }
175
176 return tokens;
177}
178
179function filterPatterns(arr, comparison, prefix, intersection) {
180 var len = arr.length, i = -1;
181 var intersected = [];
182 var res = [];
183
184 comparison = comparison.map(function(tok) {
185 return tok.string;
186 });
187
188 while (++i < len) {
189 var tok = arr[i];
190 var ele = tok.string;
191
192 if (!intersection && comparison.indexOf(ele) === -1) {
193 res.push(prefix + ele);
194 }
195 if (intersection && comparison.indexOf(ele) !== -1) {
196 intersected.push(prefix + ele);
197 }
198 }
199 return intersection ? intersected : res;
200}
201
202function countNines(num, len) {
203 return String(num).slice(0, -len) + repeat('9', len);
204}
205
206function countZeros(integer, zeros) {
207 return integer - (integer % Math.pow(10, zeros));
208}
209
210function toQuantifier(digits) {
211 var start = digits[0];
212 var stop = digits[1] ? (',' + digits[1]) : '';
213 if (!stop && (!start || start === 1)) {
214 return '';
215 }
216 return '{' + start + stop + '}';
217}
218
219function toRange(a, b) {
220 return '[' + a + '-' + b + ']';
221}
222
223function compare(a, b) {
224 return a > b ? 1 : b > a ? -1 : 0;
225}
226
227function push(arr, ele) {
228 if (arr.indexOf(ele) === -1) arr.push(ele);
229 return arr;
230}
231
232/**
233 * Expose `toRegexRange`
234 */
235
236module.exports = toRegexRange;