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