1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | 'use strict';
|
9 |
|
10 | var repeat = require('repeat-string');
|
11 | var isNumber = require('is-number');
|
12 | var cache = {range: {}, rangeToPattern: {}};
|
13 |
|
14 | function 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 |
|
56 | function 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 |
|
64 | function 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 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | function 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 |
|
139 |
|
140 |
|
141 | function zip(a, b) {
|
142 | var arr = [];
|
143 | for (var ch in a) arr.push([a[ch], b[ch]]);
|
144 | return arr;
|
145 | }
|
146 |
|
147 | function 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 |
|
179 | function 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 |
|
202 | function countNines(num, len) {
|
203 | return String(num).slice(0, -len) + repeat('9', len);
|
204 | }
|
205 |
|
206 | function countZeros(integer, zeros) {
|
207 | return integer - (integer % Math.pow(10, zeros));
|
208 | }
|
209 |
|
210 | function 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 |
|
219 | function toRange(a, b) {
|
220 | return '[' + a + '-' + b + ']';
|
221 | }
|
222 |
|
223 | function compare(a, b) {
|
224 | return a > b ? 1 : b > a ? -1 : 0;
|
225 | }
|
226 |
|
227 | function push(arr, ele) {
|
228 | if (arr.indexOf(ele) === -1) arr.push(ele);
|
229 | return arr;
|
230 | }
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | module.exports = toRegexRange;
|