171 lines
5.5 KiB
JavaScript

/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import * as JsDiff from "diff";
import OperationError from "../errors/OperationError.mjs";
/**
* Diff operation
*/
class Diff extends Operation {
/**
* Diff constructor
*/
constructor() {
super();
this.name = "Diff";
this.module = "Diff";
this.description = "Compares two inputs (separated by the specified delimiter) and highlights the differences between them.";
this.infoURL = "https://wikipedia.org/wiki/File_comparison";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
"name": "Sample delimiter",
"type": "binaryString",
"value": "\\n\\n"
},
{
"name": "Diff by",
"type": "option",
"value": ["Character", "Word", "Line", "Sentence", "CSS", "JSON"]
},
{
"name": "Show added",
"type": "boolean",
"value": true
},
{
"name": "Show removed",
"type": "boolean",
"value": true
},
{
"name": "Show subtraction",
"type": "boolean",
"value": false
},
{
"name": "Ignore whitespace",
"type": "boolean",
"value": false,
"hint": "Relevant for word and line"
},
{
"name": "Show +/- notation",
"hint": "Works only in Line diff mode.",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const [
sampleDelim,
diffBy,
showAdded,
showRemoved,
showSubtraction,
ignoreWhitespace,
showNotation
] = args,
samples = input.split(sampleDelim);
let output = "",
diff;
// Node and Webpack load modules slightly differently
const jsdiff = JsDiff.default ? JsDiff.default : JsDiff;
if (!samples || samples.length !== 2) {
throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?");
}
// +/- notation only works properly with Line diff mode
// For other modes, it falls back to HTML format and this option is ignored (see hint).
const useNotation = showNotation && diffBy === "Line";
if (useNotation) {
const patch = jsdiff.createTwoFilesPatch("original", "modified", samples[0], samples[1], "", "", {
context: Infinity,
ignoreWhitespace: ignoreWhitespace,
});
const lines = patch.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("@@")) {
if (showSubtraction) output += line + "\n";
} else if (line.startsWith("+") && !line.startsWith("+++")) {
if (showAdded) output += line + "\n";
} else if (line.startsWith("-") && !line.startsWith("---")) {
if (showRemoved) output += line + "\n";
} else if (line.startsWith("===") || line.startsWith("---") || line.startsWith("+++") || line.startsWith("\\")) {
continue;
} else if (line.length > 0 && !showSubtraction) {
output += line.substring(1) + "\n";
}
}
return output.slice(0, -1);
}
switch (diffBy) {
case "Character":
diff = jsdiff.diffChars(samples[0], samples[1]);
break;
case "Word":
if (ignoreWhitespace) {
diff = jsdiff.diffWords(samples[0], samples[1]);
} else {
diff = jsdiff.diffWordsWithSpace(samples[0], samples[1]);
}
break;
case "Line":
if (ignoreWhitespace) {
diff = jsdiff.diffTrimmedLines(samples[0], samples[1]);
} else {
diff = jsdiff.diffLines(samples[0], samples[1]);
}
break;
case "Sentence":
diff = jsdiff.diffSentences(samples[0], samples[1]);
break;
case "CSS":
diff = jsdiff.diffCss(samples[0], samples[1]);
break;
case "JSON":
diff = jsdiff.diffJson(samples[0], samples[1]);
break;
default:
throw new OperationError("Invalid 'Diff by' option.");
}
for (let i = 0; i < diff.length; i++) {
if (diff[i].added) {
if (showAdded) output += "<ins>" + Utils.escapeHtml(diff[i].value) + "</ins>";
} else if (diff[i].removed) {
if (showRemoved) output += "<del>" + Utils.escapeHtml(diff[i].value) + "</del>";
} else if (!showSubtraction) {
output += Utils.escapeHtml(diff[i].value);
}
}
return output;
}
}
export default Diff;