1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const quoteMeta = str => {
|
9 | return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
|
10 | };
|
11 |
|
12 | const toSimpleString = str => {
|
13 | if (`${+str}` === str) {
|
14 | return str;
|
15 | }
|
16 | return JSON.stringify(str);
|
17 | };
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | const compileBooleanMatcher = map => {
|
24 | const positiveItems = Object.keys(map).filter(i => map[i]);
|
25 | const negativeItems = Object.keys(map).filter(i => !map[i]);
|
26 | if (positiveItems.length === 0) return false;
|
27 | if (negativeItems.length === 0) return true;
|
28 | return compileBooleanMatcherFromLists(positiveItems, negativeItems);
|
29 | };
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | const compileBooleanMatcherFromLists = (positiveItems, negativeItems) => {
|
37 | if (positiveItems.length === 0) return () => "false";
|
38 | if (negativeItems.length === 0) return () => "true";
|
39 | if (positiveItems.length === 1)
|
40 | return value => `${toSimpleString(positiveItems[0])} == ${value}`;
|
41 | if (negativeItems.length === 1)
|
42 | return value => `${toSimpleString(negativeItems[0])} != ${value}`;
|
43 | const positiveRegexp = itemsToRegexp(positiveItems);
|
44 | const negativeRegexp = itemsToRegexp(negativeItems);
|
45 | if (positiveRegexp.length <= negativeRegexp.length) {
|
46 | return value => `/^${positiveRegexp}$/.test(${value})`;
|
47 | } else {
|
48 | return value => `!/^${negativeRegexp}$/.test(${value})`;
|
49 | }
|
50 | };
|
51 |
|
52 | const popCommonItems = (itemsSet, getKey, condition) => {
|
53 | const map = new Map();
|
54 | for (const item of itemsSet) {
|
55 | const key = getKey(item);
|
56 | if (key) {
|
57 | let list = map.get(key);
|
58 | if (list === undefined) {
|
59 | list = [];
|
60 | map.set(key, list);
|
61 | }
|
62 | list.push(item);
|
63 | }
|
64 | }
|
65 | const result = [];
|
66 | for (const list of map.values()) {
|
67 | if (condition(list)) {
|
68 | for (const item of list) {
|
69 | itemsSet.delete(item);
|
70 | }
|
71 | result.push(list);
|
72 | }
|
73 | }
|
74 | return result;
|
75 | };
|
76 |
|
77 | const getCommonPrefix = items => {
|
78 | let prefix = items[0];
|
79 | for (let i = 1; i < items.length; i++) {
|
80 | const item = items[i];
|
81 | for (let p = 0; p < prefix.length; p++) {
|
82 | if (item[p] !== prefix[p]) {
|
83 | prefix = prefix.slice(0, p);
|
84 | break;
|
85 | }
|
86 | }
|
87 | }
|
88 | return prefix;
|
89 | };
|
90 |
|
91 | const getCommonSuffix = items => {
|
92 | let suffix = items[0];
|
93 | for (let i = 1; i < items.length; i++) {
|
94 | const item = items[i];
|
95 | for (let p = item.length - 1, s = suffix.length - 1; s >= 0; p--, s--) {
|
96 | if (item[p] !== suffix[s]) {
|
97 | suffix = suffix.slice(s + 1);
|
98 | break;
|
99 | }
|
100 | }
|
101 | }
|
102 | return suffix;
|
103 | };
|
104 |
|
105 | const itemsToRegexp = itemsArr => {
|
106 | if (itemsArr.length === 1) {
|
107 | return quoteMeta(itemsArr[0]);
|
108 | }
|
109 | const finishedItems = [];
|
110 |
|
111 |
|
112 | let countOfSingleCharItems = 0;
|
113 | for (const item of itemsArr) {
|
114 | if (item.length === 1) {
|
115 | countOfSingleCharItems++;
|
116 | }
|
117 | }
|
118 |
|
119 | if (countOfSingleCharItems === itemsArr.length) {
|
120 | return `[${quoteMeta(itemsArr.sort().join(""))}]`;
|
121 | }
|
122 | const items = new Set(itemsArr.sort());
|
123 | if (countOfSingleCharItems > 2) {
|
124 | let singleCharItems = "";
|
125 | for (const item of items) {
|
126 | if (item.length === 1) {
|
127 | singleCharItems += item;
|
128 | items.delete(item);
|
129 | }
|
130 | }
|
131 | finishedItems.push(`[${quoteMeta(singleCharItems)}]`);
|
132 | }
|
133 |
|
134 |
|
135 | if (finishedItems.length === 0 && items.size === 2) {
|
136 | const prefix = getCommonPrefix(itemsArr);
|
137 | const suffix = getCommonSuffix(
|
138 | itemsArr.map(item => item.slice(prefix.length))
|
139 | );
|
140 | if (prefix.length > 0 || suffix.length > 0) {
|
141 | return `${quoteMeta(prefix)}${itemsToRegexp(
|
142 | itemsArr.map(i => i.slice(prefix.length, -suffix.length || undefined))
|
143 | )}${quoteMeta(suffix)}`;
|
144 | }
|
145 | }
|
146 |
|
147 |
|
148 | if (finishedItems.length === 0 && items.size === 2) {
|
149 | const it = items[Symbol.iterator]();
|
150 | const a = it.next().value;
|
151 | const b = it.next().value;
|
152 | if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) {
|
153 | return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta(
|
154 | a.slice(-1)
|
155 | )}`;
|
156 | }
|
157 | }
|
158 |
|
159 |
|
160 | const prefixed = popCommonItems(
|
161 | items,
|
162 | item => (item.length >= 1 ? item[0] : false),
|
163 | list => {
|
164 | if (list.length >= 3) return true;
|
165 | if (list.length <= 1) return false;
|
166 | return list[0][1] === list[1][1];
|
167 | }
|
168 | );
|
169 | for (const prefixedItems of prefixed) {
|
170 | const prefix = getCommonPrefix(prefixedItems);
|
171 | finishedItems.push(
|
172 | `${quoteMeta(prefix)}${itemsToRegexp(
|
173 | prefixedItems.map(i => i.slice(prefix.length))
|
174 | )}`
|
175 | );
|
176 | }
|
177 |
|
178 |
|
179 | const suffixed = popCommonItems(
|
180 | items,
|
181 | item => (item.length >= 1 ? item.slice(-1) : false),
|
182 | list => {
|
183 | if (list.length >= 3) return true;
|
184 | if (list.length <= 1) return false;
|
185 | return list[0].slice(-2) === list[1].slice(-2);
|
186 | }
|
187 | );
|
188 | for (const suffixedItems of suffixed) {
|
189 | const suffix = getCommonSuffix(suffixedItems);
|
190 | finishedItems.push(
|
191 | `${itemsToRegexp(
|
192 | suffixedItems.map(i => i.slice(0, -suffix.length))
|
193 | )}${quoteMeta(suffix)}`
|
194 | );
|
195 | }
|
196 |
|
197 |
|
198 |
|
199 | const conditional = finishedItems.concat(Array.from(items, quoteMeta));
|
200 | if (conditional.length === 1) return conditional[0];
|
201 | return `(${conditional.join("|")})`;
|
202 | };
|
203 |
|
204 | compileBooleanMatcher.fromLists = compileBooleanMatcherFromLists;
|
205 | compileBooleanMatcher.itemsToRegexp = itemsToRegexp;
|
206 | module.exports = compileBooleanMatcher;
|