137 lines
4.1 KiB
JavaScript
137 lines
4.1 KiB
JavaScript
/**
|
|
* @param {string} str
|
|
* @returns {string[]}
|
|
*/
|
|
const extractConcernedLines = (str) => str
|
|
.split(/,?\s*\r?\n/)
|
|
.map(s => s.trim())
|
|
.filter(s => s && s.match(/\w/) && !s.includes('@Updated'));
|
|
|
|
/**
|
|
* @param {Array} a1
|
|
* @param {Array} a2
|
|
* @returns {Set}
|
|
*/
|
|
const findCommonSet = (a1, a2) => {
|
|
const s2 = new Set(a2);
|
|
return new Set(a1.filter(l => s2.has(l)));
|
|
};
|
|
|
|
/**
|
|
* @param {string} src
|
|
* @param {string} other
|
|
* @param {Object} [options={}]
|
|
* @param {RegExp} [options.regexForKeyMatching]
|
|
* @return {void}
|
|
*/
|
|
export function printDifferences(src, other, options = {}) {
|
|
const leftRaw = extractConcernedLines(src);
|
|
const rightRaw = extractConcernedLines(other);
|
|
const commonSet = findCommonSet(leftRaw, rightRaw);
|
|
const left = leftRaw.filter(l => !commonSet.has(l));
|
|
const right = rightRaw.filter(r => !commonSet.has(r));
|
|
const minLineLength = Math.min(...[ ...left, ...right ].map(l => l.length));
|
|
|
|
const keyOf = (/** @type {string} */ line) => {
|
|
const reKey = options.regexForKeyMatching ?? null;
|
|
if (reKey) {
|
|
const m = line.match(reKey);
|
|
return m ? m[0] : line;
|
|
}
|
|
const reClassicMap = /^"([^"]+)"\s+to\s+("[^"]+"|\d+)$/;
|
|
const matchedMap = line.match(reClassicMap);
|
|
if (matchedMap) {
|
|
return matchedMap[1];
|
|
}
|
|
const reClassicVersion = /^"(\d+(\.\d+)?)(\.\d+)(\s*[-_]\w+\s*)*"$/;
|
|
const matchedVersion = line.match(reClassicVersion);
|
|
if (matchedVersion) {
|
|
return matchedVersion[1];
|
|
}
|
|
return line.slice(0, Math.max(1, Math.ceil(minLineLength * 0.4))).trimEnd();
|
|
};
|
|
|
|
const lMap = new Map();
|
|
const rMap = new Map();
|
|
left.forEach(l => {
|
|
const k = keyOf(l);
|
|
if (!lMap.has(k)) lMap.set(k, [ l ]);
|
|
else lMap.get(k).push(l);
|
|
});
|
|
right.forEach(r => {
|
|
const k = keyOf(r);
|
|
if (!rMap.has(k)) rMap.set(k, [ r ]);
|
|
else rMap.get(k).push(r);
|
|
});
|
|
|
|
const deletions = [];
|
|
const modifications = [];
|
|
const additions = [];
|
|
|
|
for (const l of left) {
|
|
const k = keyOf(l);
|
|
if (!rMap.has(k)) {
|
|
deletions.push(`-- ${l}`);
|
|
} else {
|
|
const lLineList = lMap.get(k);
|
|
const rLineList = rMap.get(k);
|
|
const rLine = rLineList?.[0];
|
|
const shouldRecordModification = lLineList.length === 1 && rLineList.length === 1 && l !== rLine;
|
|
if (shouldRecordModification) {
|
|
modifications.push({ from: `-- ${l}`, to: `-> ${rLine}` });
|
|
} else {
|
|
deletions.push(`-- ${l}`);
|
|
}
|
|
}
|
|
}
|
|
for (const r of right) {
|
|
const k = keyOf(r);
|
|
if (!lMap.has(k)) {
|
|
additions.push(`++ ${r}`);
|
|
} else {
|
|
const lLineList = lMap.get(k);
|
|
const rLineList = rMap.get(k);
|
|
const lLine = lLineList?.[0];
|
|
const shouldRecordModification = lLineList.length === 1 && rLineList.length === 1 && r !== lLine;
|
|
if (!shouldRecordModification) {
|
|
additions.push(`++ ${r}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const allLines = [
|
|
...deletions,
|
|
...modifications.flatMap(m => [ m.from, m.to ]),
|
|
...additions,
|
|
];
|
|
const maxLen = allLines.reduce((mx, s) => Math.max(mx, s.length), 0);
|
|
const SEP_EQ = '='.repeat(Math.max(6, maxLen));
|
|
const SEP_DASH = '-'.repeat(Math.max(6, maxLen));
|
|
|
|
const sections = [];
|
|
|
|
if (deletions.length > 0) {
|
|
sections.push(deletions.slice());
|
|
}
|
|
if (modifications.length > 0) {
|
|
const modLines = [];
|
|
modifications.forEach((m, idx) => {
|
|
if (idx > 0) modLines.push(SEP_DASH);
|
|
modLines.push(m.from);
|
|
modLines.push(m.to);
|
|
});
|
|
sections.push(modLines);
|
|
}
|
|
if (additions.length > 0) {
|
|
sections.push(additions.slice());
|
|
}
|
|
|
|
sections.forEach((lines, idx) => {
|
|
console.log(SEP_EQ);
|
|
lines.forEach(l => console.log(l));
|
|
if (idx === sections.length - 1) {
|
|
console.log(SEP_EQ);
|
|
}
|
|
});
|
|
}
|