mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
347 lines
9.7 KiB
TypeScript
347 lines
9.7 KiB
TypeScript
/**
|
|
*
|
|
* @param textOld - old secret
|
|
* @param textNew - new (updated) secret
|
|
* @param diffPlusFlag - a flag for whether we want to detect moving segments
|
|
* - doesn't work in some examples (e.g., when we have a full reverse ordering of the text)
|
|
* @returns
|
|
*/
|
|
function patienceDiff(textOld: string[], textNew: string[], diffPlusFlag?: boolean) {
|
|
|
|
/**
|
|
* findUnique finds all unique values in arr[lo..hi], inclusive. This
|
|
* function is used in preparation for determining the longest common
|
|
* subsequence. Specifically, it first reduces the array range in question
|
|
* to unique values.
|
|
* @param chars - an array of characters
|
|
* @param lo
|
|
* @param hi
|
|
* @returns - an ordered Map, with the arr[i] value as the Map key and the
|
|
* array index i as the Map value.
|
|
*/
|
|
function findUnique(chars: string[], lo: number, hi: number) {
|
|
const characterMap = new Map();
|
|
|
|
for (let i=lo; i<=hi; i++) {
|
|
const character = chars[i];
|
|
|
|
if (characterMap.has(character)) {
|
|
characterMap.get(character).count++;
|
|
characterMap.get(character).index = i;
|
|
} else {
|
|
characterMap.set(character, { count: 1, index: i });
|
|
}
|
|
}
|
|
|
|
characterMap.forEach((val, key, map) => {
|
|
if (val.count !== 1) {
|
|
map.delete(key);
|
|
} else {
|
|
map.set(key, val.index);
|
|
}
|
|
});
|
|
|
|
return characterMap;
|
|
}
|
|
|
|
/**
|
|
* @param aArray
|
|
* @param aLo
|
|
* @param aHi
|
|
* @param bArray
|
|
* @param bLo
|
|
* @param bHi
|
|
* @returns an ordered Map, with the Map key as the common line between aArray
|
|
* and bArray, with the Map value as an object containing the array indexes of
|
|
* the matching unique lines.
|
|
*
|
|
*/
|
|
function uniqueCommon(aArray: string[], aLo: number, aHi: number, bArray: string[], bLo: number, bHi: number) {
|
|
const ma = findUnique(aArray, aLo, aHi);
|
|
const mb = findUnique(bArray, bLo, bHi);
|
|
|
|
ma.forEach((val, key, map) => {
|
|
if (mb.has(key)) {
|
|
map.set(key, {
|
|
indexA: val,
|
|
indexB: mb.get(key)
|
|
});
|
|
} else {
|
|
map.delete(key);
|
|
}
|
|
});
|
|
|
|
return ma;
|
|
}
|
|
|
|
/**
|
|
* longestCommonSubsequence takes an ordered Map from the function uniqueCommon
|
|
* and determines the Longest Common Subsequence (LCS).
|
|
* @param abMap
|
|
* @returns an ordered array of objects containing the array indexes of the
|
|
* matching lines for a LCS.
|
|
*/
|
|
function longestCommonSubsequence(abMap: Map<number, { indexA: number, indexB: number, prev?: number }>) {
|
|
const ja: any = [];
|
|
|
|
// First, walk the list creating the jagged array.
|
|
abMap.forEach((val, key, map) => {
|
|
let i = 0;
|
|
|
|
while (ja[i] && ja[i][ja[i].length - 1].indexB < val.indexB) {
|
|
i++;
|
|
}
|
|
|
|
if (!ja[i]) {
|
|
ja[i] = [];
|
|
}
|
|
|
|
if (0 < i) {
|
|
val.prev = ja[i-1][ja[i - 1].length - 1];
|
|
}
|
|
ja[i].push(val);
|
|
});
|
|
|
|
// Now, pull out the longest common subsequence.
|
|
let lcs: any[] = [];
|
|
|
|
if (0 < ja.length) {
|
|
const n = ja.length - 1;
|
|
lcs = [ja[n][ja[n].length - 1]];
|
|
|
|
while (lcs[lcs.length - 1].prev) {
|
|
lcs.push(lcs[lcs.length - 1].prev);
|
|
}
|
|
}
|
|
|
|
return lcs.reverse();
|
|
}
|
|
|
|
// "result" is the array used to accumulate the textOld that are deleted, the
|
|
// lines that are shared between textOld and textNew, and the textNew that were
|
|
// inserted.
|
|
|
|
const result: any[] = [];
|
|
let deleted = 0;
|
|
let inserted = 0;
|
|
|
|
// aMove and bMove will contain the lines that don't match, and will be returned
|
|
// for possible searching of lines that moved.
|
|
|
|
const aMove: any[] = [];
|
|
const aMoveIndex: any[] = [];
|
|
const bMove: any[] = [];
|
|
const bMoveIndex: any[] = [];
|
|
|
|
/**
|
|
* addToResult simply pushes the latest value onto the "result" array. This
|
|
* array captures the diff of the line, aIndex, and bIndex from the textOld
|
|
* and textNew array.
|
|
* @param aIndex
|
|
* @param bIndex
|
|
*/
|
|
function addToResult(aIndex: number, bIndex: number) {
|
|
if (bIndex < 0) {
|
|
aMove.push(textOld[aIndex]);
|
|
aMoveIndex.push(result.length);
|
|
deleted++;
|
|
} else if (aIndex < 0) {
|
|
bMove.push(textNew[bIndex]);
|
|
bMoveIndex.push(result.length);
|
|
inserted++;
|
|
}
|
|
|
|
result.push({
|
|
line: 0 <= aIndex ? textOld[aIndex] : textNew[bIndex],
|
|
aIndex: aIndex,
|
|
bIndex: bIndex,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* addSubMatch handles the lines between a pair of entries in the LCS. Thus,
|
|
* this function might recursively call recurseLCS to further match the lines
|
|
* between textOld and textNew.
|
|
* @param aLo
|
|
* @param aHi
|
|
* @param bLo
|
|
* @param bHi
|
|
*/
|
|
function addSubMatch(aLo: number, aHi: number, bLo: number, bHi: number) {
|
|
// Match any lines at the beginning of textOld and textNew.
|
|
while (aLo <= aHi && bLo <= bHi && textOld[aLo] === textNew[bLo]) {
|
|
addToResult(aLo++, bLo++);
|
|
}
|
|
|
|
// Match any lines at the end of textOld and textNew, but don't place them
|
|
// in the "result" array just yet, as the lines between these matches at
|
|
// the beginning and the end need to be analyzed first.
|
|
|
|
const aHiTemp = aHi;
|
|
while (aLo <= aHi && bLo <= bHi && textOld[aHi] === textNew[bHi]) {
|
|
aHi--;
|
|
bHi--;
|
|
}
|
|
|
|
// Now, check to determine with the remaining lines in the subsequence
|
|
// whether there are any unique common lines between textOld and textNew.
|
|
//
|
|
// If not, add the subsequence to the result (all textOld having been
|
|
// deleted, and all textNew having been inserted).
|
|
//
|
|
// If there are unique common lines between textOld and textNew, then let's
|
|
// recursively perform the patience diff on the subsequence.
|
|
|
|
const uniqueCommonMap = uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi);
|
|
|
|
if (uniqueCommonMap.size === 0) {
|
|
while (aLo <= aHi) {
|
|
addToResult(aLo++, -1);
|
|
}
|
|
|
|
while (bLo <= bHi) {
|
|
addToResult(-1, bLo++);
|
|
}
|
|
} else {
|
|
recurseLCS(aLo, aHi, bLo, bHi, uniqueCommonMap);
|
|
}
|
|
|
|
// Finally, let's add the matches at the end to the result.
|
|
while (aHi < aHiTemp) {
|
|
addToResult(++aHi, ++bHi);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* recurseLCS finds the longest common subsequence (LCS) between the arrays
|
|
* textOld[aLo..aHi] and textNew[bLo..bHi] inclusive. Then for each subsequence
|
|
* recursively performs another LCS search (via addSubMatch), until there are
|
|
* none found, at which point the subsequence is dumped to the result.
|
|
* @param aLo
|
|
* @param aHi
|
|
* @param bLo
|
|
* @param bHi
|
|
* @param uniqueCommonMap
|
|
*/
|
|
function recurseLCS(aLo: number, aHi: number, bLo: number, bHi: number, uniqueCommonMap?: any) {
|
|
const x = longestCommonSubsequence(uniqueCommonMap || uniqueCommon(textOld, aLo, aHi, textNew, bLo, bHi));
|
|
|
|
if (x.length === 0) {
|
|
addSubMatch(aLo, aHi, bLo, bHi);
|
|
} else {
|
|
if (aLo < x[0].indexA || bLo < x[0].indexB) {
|
|
addSubMatch(aLo, x[0].indexA - 1, bLo, x[0].indexB - 1);
|
|
}
|
|
|
|
let i;
|
|
for (i = 0; i < x.length - 1; i++) {
|
|
addSubMatch(x[i].indexA, x[i+1].indexA - 1, x[i].indexB, x[i+1].indexB - 1);
|
|
}
|
|
|
|
if (x[i].indexA <= aHi || x[i].indexB <= bHi) {
|
|
addSubMatch(x[i].indexA, aHi, x[i].indexB, bHi);
|
|
}
|
|
}
|
|
}
|
|
|
|
recurseLCS(0, textOld.length - 1, 0, textNew.length - 1);
|
|
|
|
if (diffPlusFlag) {
|
|
return {
|
|
lines: result,
|
|
lineCountDeleted: deleted,
|
|
lineCountInserted: inserted,
|
|
lineCountMoved: 0,
|
|
aMove: aMove,
|
|
aMoveIndex: aMoveIndex,
|
|
bMove: bMove,
|
|
bMoveIndex: bMoveIndex,
|
|
};
|
|
}
|
|
|
|
return {
|
|
lines: result,
|
|
lineCountDeleted: deleted,
|
|
lineCountInserted: inserted,
|
|
lineCountMoved: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* use: patienceDiffPlus( textOld[], textNew[] )
|
|
*
|
|
* where:
|
|
* textOld[] contains the original text lines.
|
|
* textNew[] contains the new text lines.
|
|
*
|
|
* returns an object with the following properties:
|
|
* lines[] with properties of:
|
|
* line containing the line of text from textOld or textNew.
|
|
* aIndex referencing the index in aLine[].
|
|
* bIndex referencing the index in textNew[].
|
|
* (Note: The line is text from either textOld or textNew, with aIndex and bIndex
|
|
* referencing the original index. If aIndex === -1 then the line is new from textNew,
|
|
* and if bIndex === -1 then the line is old from textOld.)
|
|
* moved is true if the line was moved from elsewhere in textOld[] or textNew[].
|
|
* lineCountDeleted is the number of lines from textOld[] not appearing in textNew[].
|
|
* lineCountInserted is the number of lines from textNew[] not appearing in textOld[].
|
|
* lineCountMoved is the number of lines that moved.
|
|
*/
|
|
|
|
function patienceDiffPlus(textOld: string[], textNew: string[]) {
|
|
|
|
const difference = patienceDiff(textOld, textNew, true);
|
|
|
|
let aMoveNext = difference.aMove;
|
|
let aMoveIndexNext = difference.aMoveIndex;
|
|
let bMoveNext = difference.bMove;
|
|
let bMoveIndexNext = difference.bMoveIndex;
|
|
|
|
delete difference.aMove;
|
|
delete difference.aMoveIndex;
|
|
delete difference.bMove;
|
|
delete difference.bMoveIndex;
|
|
|
|
let lastLineCountMoved;
|
|
|
|
do {
|
|
const aMove = aMoveNext;
|
|
const aMoveIndex = aMoveIndexNext;
|
|
const bMove = bMoveNext;
|
|
const bMoveIndex = bMoveIndexNext;
|
|
|
|
aMoveNext = [];
|
|
aMoveIndexNext = [];
|
|
bMoveNext = [];
|
|
bMoveIndexNext = [];
|
|
|
|
const subDiff = patienceDiff(aMove!, bMove!);
|
|
|
|
lastLineCountMoved = difference.lineCountMoved;
|
|
|
|
subDiff.lines.forEach((v, i) => {
|
|
|
|
if (0 <= v.aIndex && 0 <= v.bIndex) {
|
|
|
|
difference.lines[aMoveIndex![v.aIndex]].moved = true;
|
|
difference.lines[bMoveIndex![v.bIndex]].aIndex = aMoveIndex![v.aIndex];
|
|
difference.lines[bMoveIndex![v.bIndex]].moved = true;
|
|
difference.lineCountInserted--;
|
|
difference.lineCountDeleted--;
|
|
difference.lineCountMoved++;
|
|
} else if (v.bIndex < 0) {
|
|
aMoveNext!.push(aMove![v.aIndex]);
|
|
aMoveIndexNext!.push(aMoveIndex![v.aIndex]);
|
|
} else {
|
|
bMoveNext!.push(bMove![v.bIndex]);
|
|
bMoveIndexNext!.push(bMoveIndex![v.bIndex]);
|
|
}
|
|
});
|
|
} while (0 < difference.lineCountMoved - lastLineCountMoved);
|
|
|
|
return difference;
|
|
|
|
}
|
|
|
|
export default patienceDiff;
|