UNPKG

@plugjs/plug

Version:
257 lines (256 loc) 7.58 kB
// utils/diff.ts import { fail } from "node:assert"; import { inspect, isDeepStrictEqual } from "node:util"; import { assert } from "../asserts.mjs"; import { $grn, $red, logOptions } from "../logging.mjs"; function compareLongestCommonSubsequence(lhsCtx, rhsCtx) { let lhsStart = 0; let rhsStart = 0; let lhsItem = 0; let rhsItem = 0; const changes = []; while (lhsItem < lhsCtx.length || rhsItem < rhsCtx.length) { if (lhsItem < lhsCtx.length && !lhsCtx.modified[lhsItem] && rhsItem < rhsCtx.length && !rhsCtx.modified[rhsItem]) { lhsItem++; rhsItem++; continue; } lhsStart = lhsItem; rhsStart = rhsItem; while (lhsItem < lhsCtx.length && (rhsItem >= rhsCtx.length || lhsCtx.modified[lhsItem])) { lhsItem++; } while (rhsItem < rhsCtx.length && (lhsItem >= lhsCtx.length || rhsCtx.modified[rhsItem])) { rhsItem++; } if (lhsStart < lhsItem || rhsStart < rhsItem) { const lat = Math.min(lhsStart, lhsCtx.length ? lhsCtx.length - 1 : 0); const rat = Math.min(rhsStart, rhsCtx.length ? rhsCtx.length - 1 : 0); changes.push({ lhsPos: lat, lhsDel: lhsItem - lhsStart, rhsPos: rat, rhsAdd: rhsItem - rhsStart }); } } return changes; } function getShortestMiddleSnake(lhsCtx, lhsLower, lhsUpper, rhsCtx, rhsLower, rhsUpper, vectorU, vectorD) { const max = lhsCtx.length + rhsCtx.length + 1; const kdown = lhsLower - rhsLower; const kup = lhsUpper - rhsUpper; const delta = lhsUpper - lhsLower - (rhsUpper - rhsLower); const odd = (delta & 1) != 0; const offsetDown = max - kdown; const offsetUp = max - kup; const maxd = (lhsUpper - lhsLower + rhsUpper - rhsLower) / 2 + 1; const ret = { x: 0, y: 0 }; let d; let k; let x; let y; vectorD[offsetDown + kdown + 1] = lhsLower; vectorU[offsetUp + kup - 1] = lhsUpper; for (d = 0; d <= maxd; ++d) { for (k = kdown - d; k <= kdown + d; k += 2) { if (k === kdown - d) { x = vectorD[offsetDown + k + 1]; } else { x = vectorD[offsetDown + k - 1] + 1; if (k < kdown + d && vectorD[offsetDown + k + 1] >= x) { x = vectorD[offsetDown + k + 1]; } } y = x - k; while (x < lhsUpper && y < rhsUpper && lhsCtx.codes[x] === rhsCtx.codes[y]) { x++; y++; } vectorD[offsetDown + k] = x; if (odd && kup - d < k && k < kup + d) { if (vectorU[offsetUp + k] <= vectorD[offsetDown + k]) { ret.x = vectorD[offsetDown + k]; ret.y = vectorD[offsetDown + k] - k; return ret; } } } for (k = kup - d; k <= kup + d; k += 2) { if (k === kup + d) { x = vectorU[offsetUp + k - 1]; } else { x = vectorU[offsetUp + k + 1] - 1; if (k > kup - d && vectorU[offsetUp + k - 1] < x) { x = vectorU[offsetUp + k - 1]; } } y = x - k; while (x > lhsLower && y > rhsLower && lhsCtx.codes[x - 1] === rhsCtx.codes[y - 1]) { x--; y--; } vectorU[offsetUp + k] = x; if (!odd && kdown - d <= k && k <= kdown + d) { if (vectorU[offsetUp + k] <= vectorD[offsetDown + k]) { ret.x = vectorD[offsetDown + k]; ret.y = vectorD[offsetDown + k] - k; return ret; } } } } fail("Unexpected state computing diff"); } function getLongestCommonSubsequence(lhsCtx, lhsLower, lhsUpper, rhsCtx, rhsLower, rhsUpper, vectorU = [], vectorD = []) { while (lhsLower < lhsUpper && rhsLower < rhsUpper && lhsCtx.codes[lhsLower] === rhsCtx.codes[rhsLower]) { ++lhsLower; ++rhsLower; } while (lhsLower < lhsUpper && rhsLower < rhsUpper && lhsCtx.codes[lhsUpper - 1] === rhsCtx.codes[rhsUpper - 1]) { --lhsUpper; --rhsUpper; } if (lhsLower === lhsUpper) { while (rhsLower < rhsUpper) { rhsCtx.modified[rhsLower++] = true; } } else if (rhsLower === rhsUpper) { while (lhsLower < lhsUpper) { lhsCtx.modified[lhsLower++] = true; } } else { const { x, y } = getShortestMiddleSnake( lhsCtx, lhsLower, lhsUpper, rhsCtx, rhsLower, rhsUpper, vectorU, vectorD ); getLongestCommonSubsequence( lhsCtx, lhsLower, x, rhsCtx, rhsLower, y, vectorU, vectorD ); getLongestCommonSubsequence( lhsCtx, x, lhsUpper, rhsCtx, y, rhsUpper, vectorU, vectorD ); } } var Context = class { /** Keep a tab on modified items */ modified; /** A _code table_ for all the items in this context */ codes; /** The number of item held by this context */ length; /** Construct with a _code table_ */ constructor(codes) { const length = this.length = codes.length; this.modified = new Array(length); this.codes = codes; } }; var Coder = class { _primitives = /* @__PURE__ */ new Map(); _objects = []; _index = 1; _getObjectCode(item) { for (const [object, code2] of this._objects) { if (isDeepStrictEqual(item, object)) return code2; } const code = ++this._index; this._objects.push([item, code]); return code; } _getPrimitiveCode(item) { let code = this._primitives.get(item); if (code) return code; code = ++this._index; this._primitives.set(item, code); return code; } /** Get the code table for an {@link Iterable} */ getCodes(iterable) { const codes = []; for (const item of iterable) { const type = item === null ? "null" : typeof item; const code = type === "object" ? this._getObjectCode(item) : this._getPrimitiveCode(item); codes.push(code); } return codes; } }; var inspectOptions = { showHidden: false, depth: 10, colors: false, maxArrayLength: 100, maxStringLength: 250, breakLength: Infinity, compact: false, sorted: true, getters: true }; function diff(lhs, rhs) { assert(lhs !== void 0, "Left-Hand side undefined"); assert(rhs !== void 0, "Right-Hand side undefined"); const codec = new Coder(); const lhsCtx = new Context(codec.getCodes(lhs)); const rhsCtx = new Context(codec.getCodes(rhs)); getLongestCommonSubsequence( lhsCtx, 0, lhsCtx.length, rhsCtx, 0, rhsCtx.length ); return compareLongestCommonSubsequence(lhsCtx, rhsCtx); } function textDiff(lhs, rhs, add, del, not) { const _add = add || (logOptions.colors ? $grn : (s) => `+ ${s}`); const _del = del || (logOptions.colors ? $red : (s) => `- ${s}`); const _not = not || (logOptions.colors ? (s) => s : (s) => ` ${s}`); let lhsLines; let rhsLines; if (typeof lhs === "string" && typeof rhs === "string") { lhsLines = lhs.split("\n"); rhsLines = rhs.split("\n"); } else { lhsLines = inspect(lhs, inspectOptions).split("\n"); rhsLines = inspect(rhs, inspectOptions).split("\n"); } const changes = diff(lhsLines, rhsLines); if (changes.length === 0) return ""; let offset = 0; const result = []; changes.forEach(({ lhsPos, lhsDel, rhsPos, rhsAdd }) => { if (offset != lhsPos) result.push(...lhsLines.slice(offset, lhsPos).map(_not)); if (lhsDel) result.push(...lhsLines.slice(lhsPos, lhsPos + lhsDel).map(_del)); if (rhsAdd) result.push(...rhsLines.slice(rhsPos, rhsPos + rhsAdd).map(_add)); offset = lhsPos + lhsDel; }); if (offset < lhsLines.length) result.push(...lhsLines.slice(offset).map(_not)); return result.join("\n"); } export { diff, textDiff }; //# sourceMappingURL=diff.mjs.map