| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 | 
							- 'use strict';
 
- const Assert = require('./assert');
 
- const DeepEqual = require('./deepEqual');
 
- const EscapeRegex = require('./escapeRegex');
 
- const Utils = require('./utils');
 
- const internals = {};
 
- module.exports = function (ref, values, options = {}) {        // options: { deep, once, only, part, symbols }
 
-     /*
 
-         string -> string(s)
 
-         array -> item(s)
 
-         object -> key(s)
 
-         object -> object (key:value)
 
-     */
 
-     if (typeof values !== 'object') {
 
-         values = [values];
 
-     }
 
-     Assert(!Array.isArray(values) || values.length, 'Values array cannot be empty');
 
-     // String
 
-     if (typeof ref === 'string') {
 
-         return internals.string(ref, values, options);
 
-     }
 
-     // Array
 
-     if (Array.isArray(ref)) {
 
-         return internals.array(ref, values, options);
 
-     }
 
-     // Object
 
-     Assert(typeof ref === 'object', 'Reference must be string or an object');
 
-     return internals.object(ref, values, options);
 
- };
 
- internals.array = function (ref, values, options) {
 
-     if (!Array.isArray(values)) {
 
-         values = [values];
 
-     }
 
-     if (!ref.length) {
 
-         return false;
 
-     }
 
-     if (options.only &&
 
-         options.once &&
 
-         ref.length !== values.length) {
 
-         return false;
 
-     }
 
-     let compare;
 
-     // Map values
 
-     const map = new Map();
 
-     for (const value of values) {
 
-         if (!options.deep ||
 
-             !value ||
 
-             typeof value !== 'object') {
 
-             const existing = map.get(value);
 
-             if (existing) {
 
-                 ++existing.allowed;
 
-             }
 
-             else {
 
-                 map.set(value, { allowed: 1, hits: 0 });
 
-             }
 
-         }
 
-         else {
 
-             compare = compare || internals.compare(options);
 
-             let found = false;
 
-             for (const [key, existing] of map.entries()) {
 
-                 if (compare(key, value)) {
 
-                     ++existing.allowed;
 
-                     found = true;
 
-                     break;
 
-                 }
 
-             }
 
-             if (!found) {
 
-                 map.set(value, { allowed: 1, hits: 0 });
 
-             }
 
-         }
 
-     }
 
-     // Lookup values
 
-     let hits = 0;
 
-     for (const item of ref) {
 
-         let match;
 
-         if (!options.deep ||
 
-             !item ||
 
-             typeof item !== 'object') {
 
-             match = map.get(item);
 
-         }
 
-         else {
 
-             for (const [key, existing] of map.entries()) {
 
-                 if (compare(key, item)) {
 
-                     match = existing;
 
-                     break;
 
-                 }
 
-             }
 
-         }
 
-         if (match) {
 
-             ++match.hits;
 
-             ++hits;
 
-             if (options.once &&
 
-                 match.hits > match.allowed) {
 
-                 return false;
 
-             }
 
-         }
 
-     }
 
-     // Validate results
 
-     if (options.only &&
 
-         hits !== ref.length) {
 
-         return false;
 
-     }
 
-     for (const match of map.values()) {
 
-         if (match.hits === match.allowed) {
 
-             continue;
 
-         }
 
-         if (match.hits < match.allowed &&
 
-             !options.part) {
 
-             return false;
 
-         }
 
-     }
 
-     return !!hits;
 
- };
 
- internals.object = function (ref, values, options) {
 
-     Assert(options.once === undefined, 'Cannot use option once with object');
 
-     const keys = Utils.keys(ref, options);
 
-     if (!keys.length) {
 
-         return false;
 
-     }
 
-     // Keys list
 
-     if (Array.isArray(values)) {
 
-         return internals.array(keys, values, options);
 
-     }
 
-     // Key value pairs
 
-     const symbols = Object.getOwnPropertySymbols(values).filter((sym) => values.propertyIsEnumerable(sym));
 
-     const targets = [...Object.keys(values), ...symbols];
 
-     const compare = internals.compare(options);
 
-     const set = new Set(targets);
 
-     for (const key of keys) {
 
-         if (!set.has(key)) {
 
-             if (options.only) {
 
-                 return false;
 
-             }
 
-             continue;
 
-         }
 
-         if (!compare(values[key], ref[key])) {
 
-             return false;
 
-         }
 
-         set.delete(key);
 
-     }
 
-     if (set.size) {
 
-         return options.part ? set.size < targets.length : false;
 
-     }
 
-     return true;
 
- };
 
- internals.string = function (ref, values, options) {
 
-     // Empty string
 
-     if (ref === '') {
 
-         return values.length === 1 && values[0] === '' ||               // '' contains ''
 
-             !options.once && !values.some((v) => v !== '');             // '' contains multiple '' if !once
 
-     }
 
-     // Map values
 
-     const map = new Map();
 
-     const patterns = [];
 
-     for (const value of values) {
 
-         Assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
 
-         if (value) {
 
-             const existing = map.get(value);
 
-             if (existing) {
 
-                 ++existing.allowed;
 
-             }
 
-             else {
 
-                 map.set(value, { allowed: 1, hits: 0 });
 
-                 patterns.push(EscapeRegex(value));
 
-             }
 
-         }
 
-         else if (options.once ||
 
-             options.only) {
 
-             return false;
 
-         }
 
-     }
 
-     if (!patterns.length) {                     // Non-empty string contains unlimited empty string
 
-         return true;
 
-     }
 
-     // Match patterns
 
-     const regex = new RegExp(`(${patterns.join('|')})`, 'g');
 
-     const leftovers = ref.replace(regex, ($0, $1) => {
 
-         ++map.get($1).hits;
 
-         return '';                              // Remove from string
 
-     });
 
-     // Validate results
 
-     if (options.only &&
 
-         leftovers) {
 
-         return false;
 
-     }
 
-     let any = false;
 
-     for (const match of map.values()) {
 
-         if (match.hits) {
 
-             any = true;
 
-         }
 
-         if (match.hits === match.allowed) {
 
-             continue;
 
-         }
 
-         if (match.hits < match.allowed &&
 
-             !options.part) {
 
-             return false;
 
-         }
 
-         // match.hits > match.allowed
 
-         if (options.once) {
 
-             return false;
 
-         }
 
-     }
 
-     return !!any;
 
- };
 
- internals.compare = function (options) {
 
-     if (!options.deep) {
 
-         return internals.shallow;
 
-     }
 
-     const hasOnly = options.only !== undefined;
 
-     const hasPart = options.part !== undefined;
 
-     const flags = {
 
-         prototype: hasOnly ? options.only : hasPart ? !options.part : false,
 
-         part: hasOnly ? !options.only : hasPart ? options.part : false
 
-     };
 
-     return (a, b) => DeepEqual(a, b, flags);
 
- };
 
- internals.shallow = function (a, b) {
 
-     return a === b;
 
- };
 
 
  |