264 lines
5.7 KiB
JavaScript
Executable file
264 lines
5.7 KiB
JavaScript
Executable file
'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;
|
|
};
|