Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MR414N-ID
GitHub Repository: MR414N-ID/botku2
Path: blob/master/node_modules/@hapi/hoek/lib/contain.js
1126 views
1
'use strict';
2
3
const Assert = require('./assert');
4
const DeepEqual = require('./deepEqual');
5
const EscapeRegex = require('./escapeRegex');
6
const Utils = require('./utils');
7
8
9
const internals = {};
10
11
12
module.exports = function (ref, values, options = {}) { // options: { deep, once, only, part, symbols }
13
14
/*
15
string -> string(s)
16
array -> item(s)
17
object -> key(s)
18
object -> object (key:value)
19
*/
20
21
if (typeof values !== 'object') {
22
values = [values];
23
}
24
25
Assert(!Array.isArray(values) || values.length, 'Values array cannot be empty');
26
27
// String
28
29
if (typeof ref === 'string') {
30
return internals.string(ref, values, options);
31
}
32
33
// Array
34
35
if (Array.isArray(ref)) {
36
return internals.array(ref, values, options);
37
}
38
39
// Object
40
41
Assert(typeof ref === 'object', 'Reference must be string or an object');
42
return internals.object(ref, values, options);
43
};
44
45
46
internals.array = function (ref, values, options) {
47
48
if (!Array.isArray(values)) {
49
values = [values];
50
}
51
52
if (!ref.length) {
53
return false;
54
}
55
56
if (options.only &&
57
options.once &&
58
ref.length !== values.length) {
59
60
return false;
61
}
62
63
let compare;
64
65
// Map values
66
67
const map = new Map();
68
for (const value of values) {
69
if (!options.deep ||
70
!value ||
71
typeof value !== 'object') {
72
73
const existing = map.get(value);
74
if (existing) {
75
++existing.allowed;
76
}
77
else {
78
map.set(value, { allowed: 1, hits: 0 });
79
}
80
}
81
else {
82
compare = compare || internals.compare(options);
83
84
let found = false;
85
for (const [key, existing] of map.entries()) {
86
if (compare(key, value)) {
87
++existing.allowed;
88
found = true;
89
break;
90
}
91
}
92
93
if (!found) {
94
map.set(value, { allowed: 1, hits: 0 });
95
}
96
}
97
}
98
99
// Lookup values
100
101
let hits = 0;
102
for (const item of ref) {
103
let match;
104
if (!options.deep ||
105
!item ||
106
typeof item !== 'object') {
107
108
match = map.get(item);
109
}
110
else {
111
compare = compare || internals.compare(options);
112
113
for (const [key, existing] of map.entries()) {
114
if (compare(key, item)) {
115
match = existing;
116
break;
117
}
118
}
119
}
120
121
if (match) {
122
++match.hits;
123
++hits;
124
125
if (options.once &&
126
match.hits > match.allowed) {
127
128
return false;
129
}
130
}
131
}
132
133
// Validate results
134
135
if (options.only &&
136
hits !== ref.length) {
137
138
return false;
139
}
140
141
for (const match of map.values()) {
142
if (match.hits === match.allowed) {
143
continue;
144
}
145
146
if (match.hits < match.allowed &&
147
!options.part) {
148
149
return false;
150
}
151
}
152
153
return !!hits;
154
};
155
156
157
internals.object = function (ref, values, options) {
158
159
Assert(options.once === undefined, 'Cannot use option once with object');
160
161
const keys = Utils.keys(ref, options);
162
if (!keys.length) {
163
return false;
164
}
165
166
// Keys list
167
168
if (Array.isArray(values)) {
169
return internals.array(keys, values, options);
170
}
171
172
// Key value pairs
173
174
const symbols = Object.getOwnPropertySymbols(values).filter((sym) => values.propertyIsEnumerable(sym));
175
const targets = [...Object.keys(values), ...symbols];
176
177
const compare = internals.compare(options);
178
const set = new Set(targets);
179
180
for (const key of keys) {
181
if (!set.has(key)) {
182
if (options.only) {
183
return false;
184
}
185
186
continue;
187
}
188
189
if (!compare(values[key], ref[key])) {
190
return false;
191
}
192
193
set.delete(key);
194
}
195
196
if (set.size) {
197
return options.part ? set.size < targets.length : false;
198
}
199
200
return true;
201
};
202
203
204
internals.string = function (ref, values, options) {
205
206
// Empty string
207
208
if (ref === '') {
209
return values.length === 1 && values[0] === '' || // '' contains ''
210
!options.once && !values.some((v) => v !== ''); // '' contains multiple '' if !once
211
}
212
213
// Map values
214
215
const map = new Map();
216
const patterns = [];
217
218
for (const value of values) {
219
Assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
220
221
if (value) {
222
const existing = map.get(value);
223
if (existing) {
224
++existing.allowed;
225
}
226
else {
227
map.set(value, { allowed: 1, hits: 0 });
228
patterns.push(EscapeRegex(value));
229
}
230
}
231
else if (options.once ||
232
options.only) {
233
234
return false;
235
}
236
}
237
238
if (!patterns.length) { // Non-empty string contains unlimited empty string
239
return true;
240
}
241
242
// Match patterns
243
244
const regex = new RegExp(`(${patterns.join('|')})`, 'g');
245
const leftovers = ref.replace(regex, ($0, $1) => {
246
247
++map.get($1).hits;
248
return ''; // Remove from string
249
});
250
251
// Validate results
252
253
if (options.only &&
254
leftovers) {
255
256
return false;
257
}
258
259
let any = false;
260
for (const match of map.values()) {
261
if (match.hits) {
262
any = true;
263
}
264
265
if (match.hits === match.allowed) {
266
continue;
267
}
268
269
if (match.hits < match.allowed &&
270
!options.part) {
271
272
return false;
273
}
274
275
// match.hits > match.allowed
276
277
if (options.once) {
278
return false;
279
}
280
}
281
282
return !!any;
283
};
284
285
286
internals.compare = function (options) {
287
288
if (!options.deep) {
289
return internals.shallow;
290
}
291
292
const hasOnly = options.only !== undefined;
293
const hasPart = options.part !== undefined;
294
295
const flags = {
296
prototype: hasOnly ? options.only : hasPart ? !options.part : false,
297
part: hasOnly ? !options.only : hasPart ? options.part : false
298
};
299
300
return (a, b) => DeepEqual(a, b, flags);
301
};
302
303
304
internals.shallow = function (a, b) {
305
306
return a === b;
307
};
308
309