You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
5.7 KiB
263 lines
5.7 KiB
'use strict'; |
|
|
|
const Assert = require('@hapi/hoek/lib/assert'); |
|
const DeepEqual = require('@hapi/hoek/lib/deepEqual'); |
|
|
|
const Common = require('./common'); |
|
|
|
|
|
const internals = {}; |
|
|
|
|
|
module.exports = internals.Values = class { |
|
|
|
constructor(values, refs) { |
|
|
|
this._values = new Set(values); |
|
this._refs = new Set(refs); |
|
this._lowercase = internals.lowercases(values); |
|
|
|
this._override = false; |
|
} |
|
|
|
get length() { |
|
|
|
return this._values.size + this._refs.size; |
|
} |
|
|
|
add(value, refs) { |
|
|
|
// Reference |
|
|
|
if (Common.isResolvable(value)) { |
|
if (!this._refs.has(value)) { |
|
this._refs.add(value); |
|
|
|
if (refs) { // Skipped in a merge |
|
refs.register(value); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
// Value |
|
|
|
if (!this.has(value, null, null, false)) { |
|
this._values.add(value); |
|
|
|
if (typeof value === 'string') { |
|
this._lowercase.set(value.toLowerCase(), value); |
|
} |
|
} |
|
} |
|
|
|
static merge(target, source, remove) { |
|
|
|
target = target || new internals.Values(); |
|
|
|
if (source) { |
|
if (source._override) { |
|
return source.clone(); |
|
} |
|
|
|
for (const item of [...source._values, ...source._refs]) { |
|
target.add(item); |
|
} |
|
} |
|
|
|
if (remove) { |
|
for (const item of [...remove._values, ...remove._refs]) { |
|
target.remove(item); |
|
} |
|
} |
|
|
|
return target.length ? target : null; |
|
} |
|
|
|
remove(value) { |
|
|
|
// Reference |
|
|
|
if (Common.isResolvable(value)) { |
|
this._refs.delete(value); |
|
return; |
|
} |
|
|
|
// Value |
|
|
|
this._values.delete(value); |
|
|
|
if (typeof value === 'string') { |
|
this._lowercase.delete(value.toLowerCase()); |
|
} |
|
} |
|
|
|
has(value, state, prefs, insensitive) { |
|
|
|
return !!this.get(value, state, prefs, insensitive); |
|
} |
|
|
|
get(value, state, prefs, insensitive) { |
|
|
|
if (!this.length) { |
|
return false; |
|
} |
|
|
|
// Simple match |
|
|
|
if (this._values.has(value)) { |
|
return { value }; |
|
} |
|
|
|
// Case insensitive string match |
|
|
|
if (typeof value === 'string' && |
|
value && |
|
insensitive) { |
|
|
|
const found = this._lowercase.get(value.toLowerCase()); |
|
if (found) { |
|
return { value: found }; |
|
} |
|
} |
|
|
|
if (!this._refs.size && |
|
typeof value !== 'object') { |
|
|
|
return false; |
|
} |
|
|
|
// Objects |
|
|
|
if (typeof value === 'object') { |
|
for (const item of this._values) { |
|
if (DeepEqual(item, value)) { |
|
return { value: item }; |
|
} |
|
} |
|
} |
|
|
|
// References |
|
|
|
if (state) { |
|
for (const ref of this._refs) { |
|
const resolved = ref.resolve(value, state, prefs, null, { in: true }); |
|
if (resolved === undefined) { |
|
continue; |
|
} |
|
|
|
const items = !ref.in || typeof resolved !== 'object' |
|
? [resolved] |
|
: Array.isArray(resolved) ? resolved : Object.keys(resolved); |
|
|
|
for (const item of items) { |
|
if (typeof item !== typeof value) { |
|
continue; |
|
} |
|
|
|
if (insensitive && |
|
value && |
|
typeof value === 'string') { |
|
|
|
if (item.toLowerCase() === value.toLowerCase()) { |
|
return { value: item, ref }; |
|
} |
|
} |
|
else { |
|
if (DeepEqual(item, value)) { |
|
return { value: item, ref }; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
override() { |
|
|
|
this._override = true; |
|
} |
|
|
|
values(options) { |
|
|
|
if (options && |
|
options.display) { |
|
|
|
const values = []; |
|
|
|
for (const item of [...this._values, ...this._refs]) { |
|
if (item !== undefined) { |
|
values.push(item); |
|
} |
|
} |
|
|
|
return values; |
|
} |
|
|
|
return Array.from([...this._values, ...this._refs]); |
|
} |
|
|
|
clone() { |
|
|
|
const set = new internals.Values(this._values, this._refs); |
|
set._override = this._override; |
|
return set; |
|
} |
|
|
|
concat(source) { |
|
|
|
Assert(!source._override, 'Cannot concat override set of values'); |
|
|
|
const set = new internals.Values([...this._values, ...source._values], [...this._refs, ...source._refs]); |
|
set._override = this._override; |
|
return set; |
|
} |
|
|
|
describe() { |
|
|
|
const normalized = []; |
|
|
|
if (this._override) { |
|
normalized.push({ override: true }); |
|
} |
|
|
|
for (const value of this._values.values()) { |
|
normalized.push(value && typeof value === 'object' ? { value } : value); |
|
} |
|
|
|
for (const value of this._refs.values()) { |
|
normalized.push(value.describe()); |
|
} |
|
|
|
return normalized; |
|
} |
|
}; |
|
|
|
|
|
internals.Values.prototype[Common.symbols.values] = true; |
|
|
|
|
|
// Aliases |
|
|
|
internals.Values.prototype.slice = internals.Values.prototype.clone; |
|
|
|
|
|
// Helpers |
|
|
|
internals.lowercases = function (from) { |
|
|
|
const map = new Map(); |
|
|
|
if (from) { |
|
for (const value of from) { |
|
if (typeof value === 'string') { |
|
map.set(value.toLowerCase(), value); |
|
} |
|
} |
|
} |
|
|
|
return map; |
|
};
|
|
|