UNPKG

@amaui/diff

Version:
594 lines (521 loc) 22 kB
/** @license AmauiDiff v1.0.11141 * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AmauiDiff = factory()); })(this, (function () { 'use strict'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var global$1 = (typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var is$1 = {exports: {}}; (function (module, exports) { Object.defineProperty(exports, "__esModule", { value: true }); const optionsDefault = {}; const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; const isNodejs = !!(typeof global$1 !== 'undefined' && 'object' !== 'undefined' && module.exports); // Multiple is methods instead of one, // so it's lighter for tree shaking usability reasons function is(type, value, options_ = {}) { var _a; const options = Object.assign(Object.assign({}, optionsDefault), options_); const { variant } = options; const prototype = value && typeof value === 'object' && Object.getPrototypeOf(value); switch (type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number' && !Number.isNaN(value); case 'boolean': return typeof value === 'boolean'; case 'array': return Array.isArray(value); case 'object': const isObject = typeof value === 'object' && !!value && value.constructor === Object; return isObject; // Map, null, WeakMap, Date, etc. case 'object-like': return typeof value === 'object' && (value === null || value.constructor !== Object); case 'class': return ((typeof value === 'object' || typeof value === 'function') && (/class/gi.test(String(value)) || /class/gi.test(String(value === null || value === void 0 ? void 0 : value.constructor)))); case 'function': return !!(value && value instanceof Function); case 'async': // If it's browser avoid calling the method // to see if it's async func or not, // where as in nodejs we have no other choice // that i know of when using transpilation // And also it might not be always correct, as // a method that returns a promise is also async // but we can't know that until the method is called and // we inspect the method's return value return !!(is('function', value) && (isBrowser ? value.constructor.name === 'AsyncFunction' : value() instanceof Promise)); case 'map': return !!(prototype === Map.prototype); case 'weakmap': return !!(prototype === WeakMap.prototype); case 'set': return !!(prototype === Set.prototype); case 'weakset': return !!(prototype === WeakSet.prototype); case 'promise': return !!(prototype === Promise.prototype); case 'int8array': return !!(prototype === Int8Array.prototype); case 'uint8array': return !!(prototype === Uint8Array.prototype); case 'uint8clampedarray': return !!(prototype === Uint8ClampedArray.prototype); case 'int16array': return !!(prototype === Int16Array.prototype); case 'uint16array': return !!(prototype === Uint16Array.prototype); case 'int32array': return !!(prototype === Int32Array.prototype); case 'uint32array': return !!(prototype === Uint32Array.prototype); case 'float32array': return !!(prototype === Float32Array.prototype); case 'float64array': return !!(prototype === Float64Array.prototype); case 'bigint64array': return !!(prototype === BigInt64Array.prototype); case 'biguint64array': return !!(prototype === BigUint64Array.prototype); case 'typedarray': return is('int8array', value) || is('uint8array', value) || is('uint8clampedarray', value) || is('int16array', value) || is('uint16array', value) || is('int32array', value) || is('uint32array', value) || is('float32array', value) || is('float64array', value) || is('bigint64array', value) || is('biguint64array', value); case 'dataview': return !!(prototype === DataView.prototype); case 'arraybuffer': return !!(prototype === ArrayBuffer.prototype); case 'sharedarraybuffer': return typeof SharedArrayBuffer !== 'undefined' && !!(prototype === SharedArrayBuffer.prototype); case 'symbol': return !!(typeof value === 'symbol'); case 'error': return !!(value && value instanceof Error); case 'date': return !!(value && value instanceof Date); case 'regexp': return !!(value && value instanceof RegExp); case 'arguments': return !!(value && value.toString() === '[object Arguments]'); case 'null': return value === null; case 'undefined': return value === undefined; case 'blob': return isBrowser && value instanceof Blob; case 'buffer': return !!(isNodejs && typeof ((_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.isBuffer) === 'function' && value.constructor.isBuffer(value)); case 'element': if (value) { switch (variant) { case undefined: case 'html': case 'element': return isBrowser && (typeof HTMLElement === 'object' ? value instanceof HTMLElement : value && typeof value === 'object' && value !== null && value.nodeType === 1 && typeof value.nodeName === 'string'); case 'node': return isBrowser && (typeof Node === 'object' ? value instanceof Node : value && typeof value === 'object' && value !== null && typeof value.nodeType === 'number' && typeof value.nodeName === 'string'); case 'react': return value.elementType || value.hasOwnProperty('$$typeof'); default: return false; } } return false; case 'simple': return (is('string', value, options) || is('number', value, options) || is('boolean', value, options) || is('undefined', value, options) || is('null', value, options)); case 'not-array-object': return !is('array', value, options) && !is('object', value, options); default: return false; } } exports.default = is; }(is$1, is$1.exports)); var is = /*@__PURE__*/getDefaultExportFromCjs(is$1.exports); var merge$1 = {}; var copy$1 = {}; Object.defineProperty(copy$1, "__esModule", { value: true }); const isArray = value => Array.isArray(value); const isObject = value => typeof value === 'object' && !!value && value.constructor === Object; // It keeps the references of the methods and classes, // unlike JSON.stringify usually used for deep simple copy const copy = (value) => { if (isArray(value)) return value.map(item => copy(item)); if (isObject(value)) { const newValue = {}; Object.keys(value).forEach(key => newValue[key] = copy(value[key])); return newValue; } return value; }; copy$1.default = copy; var __importDefault = (undefined && undefined.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(merge$1, "__esModule", { value: true }); const is_1 = __importDefault(is$1.exports); const copy_1 = __importDefault(copy$1); const optionsDefault$1 = { copy: false, merge: { array: false, }, }; const merge = (target, source, options_ = {}) => { const options = Object.assign(Object.assign({}, optionsDefault$1), options_); if (options.merge.array && (0, is_1.default)('array', target) && (0, is_1.default)('array', source)) { const length = Math.max(target.length, source.length); for (let i = 0; i < length; i++) { if (target[i] === undefined) target[i] = source[i]; if (((0, is_1.default)('object', target[i]) && (0, is_1.default)('object', source[i])) || (0, is_1.default)('array', target[i]) && (0, is_1.default)('array', source[i])) target[i] = merge(target[i], source[i], options); } } if ((0, is_1.default)('object', target) && (0, is_1.default)('object', source)) { Object.keys(source).forEach(key => { // We only care about direct target object properties // not about inherited properties from a prototype chain if (target.hasOwnProperty(key)) { if ((0, is_1.default)('object', target[key]) && (0, is_1.default)('object', source[key])) target[key] = merge(target[key], source[key], options); } else target[key] = options.copy ? (0, copy_1.default)(source[key]) : source[key]; }); } return target; }; var _default$1 = merge$1.default = merge; var stringify$1 = {}; Object.defineProperty(stringify$1, "__esModule", { value: true }); // Fix for undefined // + ref circular values const method = () => { const values = new WeakSet(); return (property, value) => { // Ref circular values const getValue = (property_, value_) => { if (typeof value_ === 'object' && value_ !== null) { if (values.has(value_)) return; values.add(value_); } return value_; }; if (getValue(property, value) === undefined || value === undefined) return undefined; return value; }; }; const stringify = (value_, spaces = 2, replacer = method()) => { try { let value = JSON.stringify(value_, replacer, spaces); // Array circular ref value update value = value // first item .replace(/(?!\[)\n* *null,/g, '') // index 1+ .replace(/,\n* *null/g, ''); return value; } catch (error) { } return String(value_); }; var _default = stringify$1.default = stringify; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } const OPTIONS = { default: { init: { method: value => String(value) }, itemize: { method: value => value.split('').filter(Boolean) }, join: { method: value => value.join('') }, equal: { method: (value, value1) => value === value1 } }, word: { init: { method: value => String(value) }, itemize: { method: value => value.split(/\s|\b/).filter(Boolean) }, join: { method: value => value.join(' ') } }, line: { init: { method: value => String(value) }, itemize: { method: value => { const values = value.split(/(\n|\r\n)/); const newValues = []; for (let i = 0; i < values.length; i++) { if (i % 2) newValues[newValues.length - 1] += values[i];else newValues.push(values[i]); } return newValues; } }, join: { method: value => value.join('') } }, sentence: { init: { method: value => String(value) }, itemize: { method: value => value.split(/(\S.*?[.!?])(?=\s +|$)/).filter(Boolean) }, join: { method: value => value.join('') } } }; const optionsDefault = _objectSpread(_objectSpread({}, OPTIONS.default), {}, { output: { compressed: true } }); class AmauiDiff { static get amauidiff() { return new AmauiDiff(); } static get word() { return new AmauiDiff(AmauiDiff.OPTIONS.word); } static get line() { return new AmauiDiff(AmauiDiff.OPTIONS.line); } static get sentence() { return new AmauiDiff(AmauiDiff.OPTIONS.sentence); } static get json() { const options = AmauiDiff.OPTIONS.line; options.init.method = value => _default(value); return new AmauiDiff(options); } static get OPTIONS() { return OPTIONS; } static updateGroups(diff) { const updateGroups = []; let previousGroup = []; let lastGroupIsRemove = false; const items = []; for (let i = 0; i < diff.items.length;) { if (['r', 'remove'].indexOf(diff.items[i]) > -1) { items.push(diff.items.slice(i, i + 2)); i += 2; } else { items.push(diff.items.slice(i, i + 3)); i += 3; } } diff.items = items; diff.items.forEach((item, index) => { const itemFollowing = diff.items[index + 1]; const action = item[0]; const actionFollow = itemFollowing && itemFollowing[0]; if (['r', 'remove'].indexOf(action) > -1 && ['r', 'remove'].indexOf(actionFollow) > -1 && !lastGroupIsRemove) { if (previousGroup.length) updateGroups.push(previousGroup); lastGroupIsRemove = true; previousGroup = []; } if (['a', 'add'].indexOf(action) > -1 && lastGroupIsRemove) { if (previousGroup.length) updateGroups.push(previousGroup); lastGroupIsRemove = false; previousGroup = []; } previousGroup.push(item); }); if (previousGroup.length) updateGroups.push(previousGroup); return updateGroups; } constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : optionsDefault; _defineProperty(this, "options", void 0); this.options = _default$1(options, optionsDefault); } diff(value_, value1_) { let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const options = _default$1(options_, this.options); const value = options.init.method(value_); const value1 = options.init.method(value1_); if (is('string', value) && is('string', value1)) { const valueArrayi = options.itemize.method(value1); const valueArrayj = options.itemize.method(value); const lengthi = valueArrayi.length; const lengthj = valueArrayj.length; const lengthMax = Math.max(lengthi, lengthj); const matrix = []; const lcs = []; const findItem = item => { if (item) { let [i, j] = item.slice(1); let left = matrix[i] && matrix[i][j - 1]; let top = matrix[i - 1] && matrix[i - 1][j]; let topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && top && left[0] !== item[0] && top[0] !== item[0]) { // Enter the new upper left matrix part [i, j] = topLeft.slice(1); } let item_; while (!item_) { left = matrix[i] && matrix[i][j - 1]; top = matrix[i - 1] && matrix[i - 1][j]; topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && left[0] === matrix[i][j][0]) { [i, j] = left.slice(1); } else if (top && top[0] === matrix[i][j][0]) { [i, j] = top.slice(1); } else { item_ = matrix[i][j]; break; } } return item_; } }; const addItem = (i, j) => { const left = matrix[i] && matrix[i][j - 1]; const top = matrix[i - 1] && matrix[i - 1][j]; const topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; const match = options.equal.method(valueArrayi[i], valueArrayj[j]); let item = 0; if (match) item = (topLeft ? topLeft[0] : 0) + 1;else item = Math.max(top ? top[0] : 0, left ? left[0] : 0); const item_ = [item, i, j]; return item_; }; // Make a matrix for (let i = 0; i < lengthi; i++) { matrix.push([]); for (let j = 0; j < lengthj; j++) { matrix[i][j] = addItem(i, j); } } // Make a lcs const matrixItem = matrix[lengthi - 1][lengthj - 1]; if (matrixItem && matrixItem[0]) { let item = matrixItem; let i = lengthi - 1; let j = lengthj - 1; const left = matrix[i] && matrix[i][j - 1]; const top = matrix[i - 1] && matrix[i - 1][j]; const topLeft = matrix[i - 1] && matrix[i - 1][j - 1]; if (left && top && left[0] !== item[0] && top[0] !== item[0]) { lcs.push(item); // Enter the new upper left matrix part [i, j] = topLeft.slice(1); } while (item && item[1] !== 0 && item[2] !== 0) { item = findItem(item); lcs.push(item); } } // Sort based on value lcs lcs.sort((a, b) => a[0] - b[0]); // Make a diff items const diff = { items: [] }; for (let i = 0; i < lengthMax; i++) { const iItem = lcs.find(item => item[1] === i); const jItem = lcs.find(item => item[2] === i); if (!jItem && i < lengthj) diff.items.push(this.action('remove', i, undefined, options)); if (!iItem && i < lengthi) diff.items.push(this.action('add', i, valueArrayi[i], options)); } function sortMethod(a, b) { if (a[0] < b[0]) return 1; if (a[0] > b[0]) return -1; return 0; } diff.items.sort(sortMethod); diff.items = diff.items.flat(); return diff; } } update(value__, diff) { let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; const options = _default$1(options_, this.options); const value_ = options.init.method(value__); if (is('string', value_) && diff !== undefined) { var _diff$items; const value = options.itemize.method(value_); if ((_diff$items = diff.items) !== null && _diff$items !== void 0 && _diff$items.length) { const updateGroups = AmauiDiff.updateGroups(diff); updateGroups.forEach(group => { const removeGroup = group.every(item => ['r', 'remove'].indexOf(item[0]) > -1); if (removeGroup) { group.sort((a, b) => b[1] - a[1]); group.forEach(item => value.splice(item[1], 1)); } else { group.forEach(item => { const action = item[0]; const index = item[1]; const valueItem = item[2]; switch (action) { case 'a': case 'add': value.splice(index, 0, valueItem); break; case 'r': case 'remove': value.splice(index, 1); break; } }); } }); } return options.join.method(value); } } action(action, index, value, options) { var _options$output; const compressed = (_options$output = options.output) === null || _options$output === void 0 ? void 0 : _options$output.compressed; return [compressed ? action.charAt(0) : action, index, value].filter(item => item !== undefined); } } var AmauiDiff$1 = AmauiDiff; return AmauiDiff$1; }));