|
|
'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; };
|