forked from zhurui/management
157 lines
4.1 KiB
JavaScript
157 lines
4.1 KiB
JavaScript
'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)
|
|
*/
|
|
|
|
let valuePairs = null;
|
|
if (typeof ref === 'object' &&
|
|
typeof values === 'object' &&
|
|
!Array.isArray(ref) &&
|
|
!Array.isArray(values)) {
|
|
|
|
valuePairs = values;
|
|
const symbols = Object.getOwnPropertySymbols(values).filter(Object.prototype.propertyIsEnumerable.bind(values));
|
|
values = [...Object.keys(values), ...symbols];
|
|
}
|
|
else {
|
|
values = [].concat(values);
|
|
}
|
|
|
|
Assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object');
|
|
Assert(values.length, 'Values array cannot be empty');
|
|
|
|
let compare;
|
|
let compareFlags;
|
|
if (options.deep) {
|
|
compare = DeepEqual;
|
|
|
|
const hasOnly = options.only !== undefined;
|
|
const hasPart = options.part !== undefined;
|
|
|
|
compareFlags = {
|
|
prototype: hasOnly ? options.only : hasPart ? !options.part : false,
|
|
part: hasOnly ? !options.only : hasPart ? options.part : false
|
|
};
|
|
}
|
|
else {
|
|
compare = (a, b) => a === b;
|
|
}
|
|
|
|
let misses = false;
|
|
const matches = new Array(values.length);
|
|
for (let i = 0; i < matches.length; ++i) {
|
|
matches[i] = 0;
|
|
}
|
|
|
|
if (typeof ref === 'string') {
|
|
if (ref === '') {
|
|
if (values.length === 1 && values[0] === '' || // '' contains ''
|
|
!options.once && !values.some((v) => v !== '')) { // '' contains multiple '' if !once
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
let pattern = '(';
|
|
for (let i = 0; i < values.length; ++i) {
|
|
const value = values[i];
|
|
Assert(typeof value === 'string', 'Cannot compare string reference to non-string value');
|
|
pattern += (i ? '|' : '') + EscapeRegex(value);
|
|
}
|
|
|
|
const regex = new RegExp(pattern + ')', 'g');
|
|
const leftovers = ref.replace(regex, ($0, $1) => {
|
|
|
|
const index = values.indexOf($1);
|
|
++matches[index];
|
|
return ''; // Remove from string
|
|
});
|
|
|
|
misses = !!leftovers;
|
|
}
|
|
else if (Array.isArray(ref)) {
|
|
if (!ref.length) {
|
|
return false;
|
|
}
|
|
|
|
const onlyOnce = !!(options.only && options.once);
|
|
if (onlyOnce && ref.length !== values.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < ref.length; ++i) {
|
|
let matched = false;
|
|
for (let j = 0; j < values.length && matched === false; ++j) {
|
|
if (!onlyOnce || matches[j] === 0) {
|
|
matched = compare(values[j], ref[i], compareFlags) && j;
|
|
}
|
|
}
|
|
|
|
if (matched !== false) {
|
|
++matches[matched];
|
|
}
|
|
else {
|
|
misses = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
const keys = Utils.keys(ref, options);
|
|
if (!keys.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < keys.length; ++i) {
|
|
const key = keys[i];
|
|
const pos = values.indexOf(key);
|
|
if (pos !== -1) {
|
|
if (valuePairs &&
|
|
!compare(valuePairs[key], ref[key], compareFlags)) {
|
|
|
|
return false;
|
|
}
|
|
|
|
++matches[pos];
|
|
}
|
|
else {
|
|
misses = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.only) {
|
|
if (misses || !options.once) {
|
|
return !misses;
|
|
}
|
|
}
|
|
|
|
let result = false;
|
|
for (let i = 0; i < matches.length; ++i) {
|
|
result = result || !!matches[i];
|
|
if ((options.once && matches[i] > 1) ||
|
|
(!options.part && !matches[i])) {
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|