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