wip: Code and Tests working
This commit is contained in:
parent
ef08c53ba9
commit
4f607633dd
48 changed files with 3318 additions and 3224 deletions
|
|
@ -3,4 +3,5 @@ coverage/**
|
|||
build/**
|
||||
docs/**
|
||||
node_modules/**
|
||||
src/diff2html-templates.js
|
||||
src/diff2html-templates.*
|
||||
typings/**
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -32,4 +32,4 @@ bower_components/
|
|||
/docs/
|
||||
/dist/
|
||||
/build/
|
||||
/src/diff2html-templates.js
|
||||
/src/diff2html-templates.*
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ The HTML output accepts a Javascript object with configuration. Possible options
|
|||
- `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
|
||||
- `maxLineSizeInBlockForComparison`: maximum number os characters of the bigger line in a block to apply comparison, default is `200`
|
||||
- `maxLineLengthHighlight`: only perform diff changes highlight if lines are smaller than this, default is `10000`
|
||||
- `templates`: object with previously compiled templates to replace parts of the html
|
||||
- `compiledTemplates`: object with previously compiled templates to replace parts of the html
|
||||
- `rawTemplates`: object with raw not compiled templates to replace parts of the html
|
||||
- `renderNothingWhenEmpty`: render nothing if the diff shows no change in its comparison: `true` or `false`, default is `false`
|
||||
> For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)
|
||||
|
|
|
|||
45
package.json
45
package.json
|
|
@ -41,7 +41,7 @@
|
|||
"coverage": "jest --collectCoverage",
|
||||
"coverage-html": "yarn run coverage && open ./coverage/index.html",
|
||||
"codacy": "cat ./coverage/lcov.info | codacy-coverage",
|
||||
"build": "rm -rf build; yarn run build-scripts && yarn run build-css && yarn run build-templates && yarn run build-library && yarn run build-browser-bundle && yarn run build-website",
|
||||
"build": "rm -rf build docs; yarn run build-scripts && yarn run build-css && yarn run build-templates && yarn run build-library && yarn run build-browser-bundle && yarn run build-website",
|
||||
"build-scripts": "tsc -p tsconfig.scripts.json",
|
||||
"build-css": "./scripts/build-css.sh",
|
||||
"build-templates": "./scripts/build-templates.sh",
|
||||
|
|
@ -57,23 +57,22 @@
|
|||
"fs": false
|
||||
},
|
||||
"dependencies": {
|
||||
"diff": "^4.0.1",
|
||||
"hogan.js": "^3.0.2",
|
||||
"merge": "^1.2.1",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
"diff": "4.0.1",
|
||||
"hogan.js": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/hogan.js": "^3.0.0",
|
||||
"@types/diff": "4.0.2",
|
||||
"@types/highlight.js": "9.12.3",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/nopt": "^3.0.29",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/node": "12.7.2",
|
||||
"@types/nopt": "3.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "2.0.0",
|
||||
"@typescript-eslint/parser": "2.0.0",
|
||||
"autoprefixer": "^9.6.0",
|
||||
"browserify": "^16.5.0",
|
||||
"clean-css-cli": "^4.3.0",
|
||||
"codacy-coverage": "^3.4.0",
|
||||
"autoprefixer": "9.6.0",
|
||||
"browserify": "16.5.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
"codacy-coverage": "3.4.0",
|
||||
"eslint": "6.2.2",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"eslint-config-standard": "14.0.1",
|
||||
|
|
@ -81,17 +80,19 @@
|
|||
"eslint-plugin-jest": "22.15.2",
|
||||
"eslint-plugin-node": "9.1.0",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"fast-html-parser": "^1.0.1",
|
||||
"eslint-plugin-promise": "4.2.1",
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"fast-html-parser": "1.0.1",
|
||||
"highlight.js": "9.15.10",
|
||||
"jest": "24.9.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nopt": "^4.0.1",
|
||||
"postcss-cli": "^6.1.3",
|
||||
"mkdirp": "0.5.1",
|
||||
"nopt": "4.0.1",
|
||||
"postcss-cli": "6.1.3",
|
||||
"prettier": "1.18.2",
|
||||
"terser": "^4.3.8",
|
||||
"ts-jest": "24.0.2",
|
||||
"typescript": "^3.6.3"
|
||||
"terser": "4.3.8",
|
||||
"ts-jest": "24.1.0",
|
||||
"typescript": "3.6.4",
|
||||
"whatwg-fetch": "3.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"lodash": "4.17.15"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ set -e
|
|||
SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )"
|
||||
|
||||
node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \
|
||||
--wrapper node \
|
||||
--variable 'browserTemplates' \
|
||||
${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.js
|
||||
--wrapper ts \
|
||||
--variable 'defaultTemplates' \
|
||||
${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.ts
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import * as path from "path";
|
|||
import * as fs from "fs";
|
||||
|
||||
import * as hogan from "hogan.js";
|
||||
import * as nopt from "nopt";
|
||||
import nopt from "nopt";
|
||||
import * as mkderp from "mkdirp";
|
||||
|
||||
const options = nopt(
|
||||
|
|
@ -52,10 +52,9 @@ function cyan(text: string): string {
|
|||
}
|
||||
|
||||
function extractFiles(files: string[]): string[] {
|
||||
const usage = `
|
||||
${cyan(
|
||||
"USAGE:"
|
||||
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
||||
const usage = `${cyan(
|
||||
"USAGE:"
|
||||
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
||||
|
||||
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd)
|
||||
[-o, --outputdir] :: outputs the templates as individual files to a directory
|
||||
|
|
@ -130,6 +129,8 @@ function wrap(file: string, name: string, openedFile: string): string {
|
|||
// If we have a template per file the export will expose the template directly
|
||||
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
|
||||
|
||||
case "ts":
|
||||
return `// @ts-ignore\n${objectStmt}`;
|
||||
default:
|
||||
return objectStmt;
|
||||
}
|
||||
|
|
@ -141,19 +142,18 @@ function prepareOutput(content: string): string {
|
|||
case "amd":
|
||||
return content;
|
||||
case "node":
|
||||
return (
|
||||
"(function() {\n" +
|
||||
"if (!!!global." +
|
||||
variableName +
|
||||
") global." +
|
||||
variableName +
|
||||
" = {};\n" +
|
||||
'var Hogan = require("hogan.js");' +
|
||||
content +
|
||||
"\n" +
|
||||
(!options.outputdir ? "module.exports = global." + variableName + ";\n" : "") +
|
||||
"})();"
|
||||
);
|
||||
return `(function() {
|
||||
if (!!!global.${variableName}) global.${variableName} = {};
|
||||
var Hogan = require("hogan.js");
|
||||
${content}
|
||||
${!options.outputdir ? `module.exports = global.${variableName};\n` : ""})();`;
|
||||
|
||||
case "ts":
|
||||
return `import * as Hogan from "hogan.js";
|
||||
type CompiledTemplates = { [name: string]: Hogan.Template };
|
||||
export const ${variableName}: CompiledTemplates = {};
|
||||
${content}`;
|
||||
|
||||
default:
|
||||
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
|
||||
}
|
||||
|
|
@ -181,7 +181,9 @@ const templates = extractFiles(options.argv.remain)
|
|||
|
||||
if (!options.outputdir) return cleanFileContents;
|
||||
|
||||
return fs.writeFileSync(path.join(options.outputdir, `${name}.js`), prepareOutput(cleanFileContents));
|
||||
const fileExtension = options.wrapper === "ts" ? "ts" : "js";
|
||||
|
||||
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
|
||||
})
|
||||
.filter(templateContents => typeof templateContents !== "undefined");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
const DiffParser = require("../diff-parser.js").DiffParser;
|
||||
import { parse } from "../diff-parser";
|
||||
|
||||
function checkDiffSample(diff) {
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
function checkDiffSample(diff: string): void {
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample").toEqual(file1.oldName);
|
||||
expect("sample").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
}
|
||||
|
||||
describe("DiffParser", function() {
|
||||
describe("generateDiffJson", function() {
|
||||
it("should parse unix with \n diff", function() {
|
||||
describe("DiffParser", () => {
|
||||
describe("generateDiffJson", () => {
|
||||
it("should parse unix with \n diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
|
|
@ -25,7 +25,7 @@ describe("DiffParser", function() {
|
|||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it("should parse windows with \r\n diff", function() {
|
||||
it("should parse windows with \r\n diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\r\n" +
|
||||
"index 0000001..0ddf2ba\r\n" +
|
||||
|
|
@ -37,7 +37,7 @@ describe("DiffParser", function() {
|
|||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it("should parse old os x with \r diff", function() {
|
||||
it("should parse old os x with \r diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\r" +
|
||||
"index 0000001..0ddf2ba\r" +
|
||||
|
|
@ -49,7 +49,7 @@ describe("DiffParser", function() {
|
|||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it("should parse mixed eols diff", function() {
|
||||
it("should parse mixed eols diff", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\r\n" +
|
||||
|
|
@ -61,7 +61,7 @@ describe("DiffParser", function() {
|
|||
checkDiffSample(diff);
|
||||
});
|
||||
|
||||
it("should parse diff with special characters", function() {
|
||||
it("should parse diff with special characters", () => {
|
||||
const diff =
|
||||
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
|
||||
"index 4c679d7..e9bd385 100644\n" +
|
||||
|
|
@ -72,17 +72,17 @@ describe("DiffParser", function() {
|
|||
"+cenas com ananas\n" +
|
||||
"+bananas";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("bla with \ttab.scala").toEqual(file1.oldName);
|
||||
expect("bla with \ttab.scala").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse diff with prefix", function() {
|
||||
it("should parse diff with prefix", () => {
|
||||
const diff =
|
||||
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
|
||||
"index 4c679d7..e9bd385 100644\n" +
|
||||
|
|
@ -93,17 +93,17 @@ describe("DiffParser", function() {
|
|||
"+cenas com ananas\n" +
|
||||
"+bananas";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("bla with \ttab.scala").toEqual(file1.oldName);
|
||||
expect("bla with \ttab.scala").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse diff with deleted file", function() {
|
||||
it("should parse diff with deleted file", () => {
|
||||
const diff =
|
||||
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
|
||||
"deleted file mode 100644\n" +
|
||||
|
|
@ -111,26 +111,26 @@ describe("DiffParser", function() {
|
|||
"--- a/src/var/strundefined.js\n" +
|
||||
"+++ /dev/null\n" +
|
||||
"@@ -1,3 +0,0 @@\n" +
|
||||
"-define(function() {\n" +
|
||||
"-define(() => {\n" +
|
||||
"- return typeof undefined;\n" +
|
||||
"-});\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(false).toEqual(file1.isCombined);
|
||||
expect(0).toEqual(file1.addedLines);
|
||||
expect(3).toEqual(file1.deletedLines);
|
||||
expect("src/var/strundefined.js").toEqual(file1.oldName);
|
||||
expect("/dev/null").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(true).toEqual(file1.isDeleted);
|
||||
expect("04e16b0").toEqual(file1.checksumBefore);
|
||||
expect("0000000").toEqual(file1.checksumAfter);
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("src/var/strundefined.js");
|
||||
expect(file1.newName).toEqual("/dev/null");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.isDeleted).toEqual(true);
|
||||
expect(file1.checksumBefore).toEqual("04e16b0");
|
||||
expect(file1.checksumAfter).toEqual("0000000");
|
||||
});
|
||||
|
||||
it("should parse diff with new file", function() {
|
||||
it("should parse diff with new file", () => {
|
||||
const diff =
|
||||
"diff --git a/test.js b/test.js\n" +
|
||||
"new file mode 100644\n" +
|
||||
|
|
@ -144,23 +144,23 @@ describe("DiffParser", function() {
|
|||
"+\n" +
|
||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(false).toEqual(file1.isCombined);
|
||||
expect(5).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("/dev/null").toEqual(file1.oldName);
|
||||
expect("test.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(true).toEqual(file1.isNew);
|
||||
expect("100644").toEqual(file1.newFileMode);
|
||||
expect("0000000").toEqual(file1.checksumBefore);
|
||||
expect("e1e22ec").toEqual(file1.checksumAfter);
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(5);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("/dev/null");
|
||||
expect(file1.newName).toEqual("test.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.isNew).toEqual(true);
|
||||
expect(file1.newFileMode).toEqual("100644");
|
||||
expect(file1.checksumBefore).toEqual("0000000");
|
||||
expect(file1.checksumAfter).toEqual("e1e22ec");
|
||||
});
|
||||
|
||||
it("should parse diff with nested diff", function() {
|
||||
it("should parse diff with nested diff", () => {
|
||||
const diff =
|
||||
"diff --git a/src/offset.js b/src/offset.js\n" +
|
||||
"index cc6ffb4..fa51f18 100644\n" +
|
||||
|
|
@ -174,22 +174,22 @@ describe("DiffParser", function() {
|
|||
"+\n" +
|
||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(false).toEqual(file1.isCombined);
|
||||
expect(6).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("src/offset.js").toEqual(file1.oldName);
|
||||
expect("src/offset.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(6).toEqual(file1.blocks[0].lines.length);
|
||||
expect("cc6ffb4").toEqual(file1.checksumBefore);
|
||||
expect("fa51f18").toEqual(file1.checksumAfter);
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(6);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("src/offset.js");
|
||||
expect(file1.newName).toEqual("src/offset.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(6);
|
||||
expect(file1.checksumBefore).toEqual("cc6ffb4");
|
||||
expect(file1.checksumAfter).toEqual("fa51f18");
|
||||
});
|
||||
|
||||
it("should parse diff with multiple blocks", function() {
|
||||
it("should parse diff with multiple blocks", () => {
|
||||
const diff =
|
||||
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
|
||||
"index c617824..c8d1393 100644\n" +
|
||||
|
|
@ -217,23 +217,23 @@ describe("DiffParser", function() {
|
|||
" // store className if set\n" +
|
||||
' dataPriv.set( this, "__className__", this.className );\n';
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(false).toEqual(file1.isCombined);
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(3).toEqual(file1.deletedLines);
|
||||
expect("src/attributes/classes.js").toEqual(file1.oldName);
|
||||
expect("src/attributes/classes.js").toEqual(file1.newName);
|
||||
expect(2).toEqual(file1.blocks.length);
|
||||
expect(11).toEqual(file1.blocks[0].lines.length);
|
||||
expect(8).toEqual(file1.blocks[1].lines.length);
|
||||
expect("c617824").toEqual(file1.checksumBefore);
|
||||
expect("c8d1393").toEqual(file1.checksumAfter);
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("src/attributes/classes.js");
|
||||
expect(file1.newName).toEqual("src/attributes/classes.js");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines.length).toEqual(11);
|
||||
expect(file1.blocks[1].lines.length).toEqual(8);
|
||||
expect(file1.checksumBefore).toEqual("c617824");
|
||||
expect(file1.checksumAfter).toEqual("c8d1393");
|
||||
});
|
||||
|
||||
it("should parse diff with multiple files", function() {
|
||||
it("should parse diff with multiple files", () => {
|
||||
const diff =
|
||||
"diff --git a/src/core/init.js b/src/core/init.js\n" +
|
||||
"index e49196a..50f310c 100644\n" +
|
||||
|
|
@ -260,33 +260,33 @@ describe("DiffParser", function() {
|
|||
' "./var/hasOwn",\n' +
|
||||
' "./var/slice",\n';
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(2).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(false).toEqual(file1.isCombined);
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("src/core/init.js").toEqual(file1.oldName);
|
||||
expect("src/core/init.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(8).toEqual(file1.blocks[0].lines.length);
|
||||
expect("e49196a").toEqual(file1.checksumBefore);
|
||||
expect("50f310c").toEqual(file1.checksumAfter);
|
||||
expect(file1.isCombined).toEqual(false);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("src/core/init.js");
|
||||
expect(file1.newName).toEqual("src/core/init.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||
expect(file1.checksumBefore).toEqual("e49196a");
|
||||
expect(file1.checksumAfter).toEqual("50f310c");
|
||||
|
||||
const file2 = result[1];
|
||||
expect(false).toEqual(file2.isCombined);
|
||||
expect(0).toEqual(file2.addedLines);
|
||||
expect(1).toEqual(file2.deletedLines);
|
||||
expect("src/event.js").toEqual(file2.oldName);
|
||||
expect("src/event.js").toEqual(file2.newName);
|
||||
expect(1).toEqual(file2.blocks.length);
|
||||
expect(6).toEqual(file2.blocks[0].lines.length);
|
||||
expect("7336f4d").toEqual(file2.checksumBefore);
|
||||
expect("6183f70").toEqual(file2.checksumAfter);
|
||||
expect(file2.isCombined).toEqual(false);
|
||||
expect(file2.addedLines).toEqual(0);
|
||||
expect(file2.deletedLines).toEqual(1);
|
||||
expect(file2.oldName).toEqual("src/event.js");
|
||||
expect(file2.newName).toEqual("src/event.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
expect(file2.blocks[0].lines.length).toEqual(6);
|
||||
expect(file2.checksumBefore).toEqual("7336f4d");
|
||||
expect(file2.checksumAfter).toEqual("6183f70");
|
||||
});
|
||||
|
||||
it("should parse combined diff", function() {
|
||||
it("should parse combined diff", () => {
|
||||
const diff =
|
||||
"diff --combined describe.c\n" +
|
||||
"index fabadb8,cc95eb0..4866510\n" +
|
||||
|
|
@ -316,62 +316,62 @@ describe("DiffParser", function() {
|
|||
" initialized = 1;\n" +
|
||||
" for_each_ref(get_name);\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(true).toEqual(file1.isCombined);
|
||||
expect(file1.isCombined).toEqual(true);
|
||||
expect(9).toEqual(file1.addedLines);
|
||||
expect(2).toEqual(file1.deletedLines);
|
||||
expect("describe.c").toEqual(file1.oldName);
|
||||
expect("describe.c").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(22).toEqual(file1.blocks[0].lines.length);
|
||||
expect(["4866510", "cc95eb0"].sort()).toEqual(file1.checksumBefore.sort());
|
||||
expect("fabadb8").toEqual(file1.checksumAfter);
|
||||
expect(file1.oldName).toEqual("describe.c");
|
||||
expect(file1.newName).toEqual("describe.c");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(22);
|
||||
expect(file1.checksumBefore).toEqual(["cc95eb0", "4866510"]);
|
||||
expect(file1.checksumAfter).toEqual("fabadb8");
|
||||
});
|
||||
|
||||
it("should parse diffs with copied files", function() {
|
||||
it("should parse diffs with copied files", () => {
|
||||
const diff =
|
||||
"diff --git a/index.js b/more-index.js\n" +
|
||||
"dissimilarity index 5%\n" +
|
||||
"copy from index.js\n" +
|
||||
"copy to more-index.js\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(0).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("index.js").toEqual(file1.oldName);
|
||||
expect("more-index.js").toEqual(file1.newName);
|
||||
expect(0).toEqual(file1.blocks.length);
|
||||
expect(true).toEqual(file1.isCopy);
|
||||
expect("5").toEqual(file1.changedPercentage);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("index.js");
|
||||
expect(file1.newName).toEqual("more-index.js");
|
||||
expect(file1.blocks.length).toEqual(0);
|
||||
expect(file1.isCopy).toEqual(true);
|
||||
expect(file1.changedPercentage).toEqual(5);
|
||||
});
|
||||
|
||||
it("should parse diffs with moved files", function() {
|
||||
it("should parse diffs with moved files", () => {
|
||||
const diff =
|
||||
"diff --git a/more-index.js b/other-index.js\n" +
|
||||
"similarity index 86%\n" +
|
||||
"rename from more-index.js\n" +
|
||||
"rename to other-index.js\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(0).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("more-index.js").toEqual(file1.oldName);
|
||||
expect("other-index.js").toEqual(file1.newName);
|
||||
expect(0).toEqual(file1.blocks.length);
|
||||
expect(true).toEqual(file1.isRename);
|
||||
expect("86").toEqual(file1.unchangedPercentage);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("more-index.js");
|
||||
expect(file1.newName).toEqual("other-index.js");
|
||||
expect(file1.blocks.length).toEqual(0);
|
||||
expect(file1.isRename).toEqual(true);
|
||||
expect(file1.unchangedPercentage).toEqual(86);
|
||||
});
|
||||
|
||||
it("should parse diffs correct line numbers", function() {
|
||||
it("should parse diffs correct line numbers", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
|
|
@ -381,23 +381,23 @@ describe("DiffParser", function() {
|
|||
"-test\n" +
|
||||
"+test1r\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(1).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample").toEqual(file1.oldName);
|
||||
expect("sample").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(2).toEqual(file1.blocks[0].lines.length);
|
||||
expect(1).toEqual(file1.blocks[0].lines[0].oldNumber);
|
||||
expect(null).toEqual(file1.blocks[0].lines[0].newNumber);
|
||||
expect(null).toEqual(file1.blocks[0].lines[1].oldNumber);
|
||||
expect(1).toEqual(file1.blocks[0].lines[1].newNumber);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines[0].oldNumber).toEqual(1);
|
||||
expect(file1.blocks[0].lines[0].newNumber).toBeUndefined();
|
||||
expect(file1.blocks[0].lines[1].oldNumber).toBeUndefined();
|
||||
expect(file1.blocks[0].lines[1].newNumber).toEqual(1);
|
||||
});
|
||||
|
||||
it("should parse unified non git diff and strip timestamps off the headers", function() {
|
||||
it("should parse unified non git diff and strip timestamps off the headers", () => {
|
||||
const diffs = [
|
||||
// 2 hours ahead of GMT
|
||||
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
|
||||
|
|
@ -416,42 +416,42 @@ describe("DiffParser", function() {
|
|||
];
|
||||
|
||||
diffs.forEach(function(diff) {
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample.js").toEqual(file1.oldName);
|
||||
expect("sample.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual(["-test", "+test1r", "+test2r"]);
|
||||
expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse unified non git diff", function() {
|
||||
it("should parse unified non git diff", () => {
|
||||
const diff =
|
||||
"--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample.js").toEqual(file1.oldName);
|
||||
expect("sample.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual(["-test", "+test1r", "+test2r"]);
|
||||
expect(["-test", "+test1r", "+test2r"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse unified diff with multiple hunks and files", function() {
|
||||
it("should parse unified diff with multiple hunks and files", () => {
|
||||
const diff =
|
||||
"--- sample.js\n" +
|
||||
"+++ sample.js\n" +
|
||||
|
|
@ -464,40 +464,40 @@ describe("DiffParser", function() {
|
|||
"@@ -1 +1,2 @@\n" +
|
||||
"+test1";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(2).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample.js").toEqual(file1.oldName);
|
||||
expect("sample.js").toEqual(file1.newName);
|
||||
expect(2).toEqual(file1.blocks.length);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
|
||||
const linesContent1 = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent1).toEqual(["-test"]);
|
||||
expect(["-test"]).toEqual(linesContent1);
|
||||
|
||||
const linesContent2 = file1.blocks[1].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent2).toEqual(["+test"]);
|
||||
expect(["+test"]).toEqual(linesContent2);
|
||||
|
||||
const file2 = result[1];
|
||||
expect(1).toEqual(file2.addedLines);
|
||||
expect(0).toEqual(file2.deletedLines);
|
||||
expect("sample1.js").toEqual(file2.oldName);
|
||||
expect("sample1.js").toEqual(file2.newName);
|
||||
expect(1).toEqual(file2.blocks.length);
|
||||
expect(file2.addedLines).toEqual(1);
|
||||
expect(file2.deletedLines).toEqual(0);
|
||||
expect(file2.oldName).toEqual("sample1.js");
|
||||
expect(file2.newName).toEqual("sample1.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file2.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual(["+test1"]);
|
||||
expect(["+test1"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff with --- and +++ in the context lines", function() {
|
||||
it("should parse diff with --- and +++ in the context lines", () => {
|
||||
const diff =
|
||||
"--- sample.js\n" +
|
||||
"+++ sample.js\n" +
|
||||
|
|
@ -512,40 +512,40 @@ describe("DiffParser", function() {
|
|||
"+++ 2\n" +
|
||||
"++++ 2";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(3).toEqual(file1.addedLines);
|
||||
expect(3).toEqual(file1.deletedLines);
|
||||
expect("sample.js").toEqual(file1.oldName);
|
||||
expect("sample.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(3);
|
||||
expect(file1.deletedLines).toEqual(3);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual([" test", " ", "-- 1", "--- 1", "---- 1", " ", "++ 2", "+++ 2", "++++ 2"]);
|
||||
expect([" test", " ", "-- 1", "--- 1", "---- 1", " ", "++ 2", "+++ 2", "++++ 2"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff without proper hunk headers", function() {
|
||||
it("should parse diff without proper hunk headers", () => {
|
||||
const diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(0).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("sample.js").toEqual(file1.oldName);
|
||||
expect("sample.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("sample.js");
|
||||
expect(file1.newName).toEqual("sample.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
|
||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual([" test"]);
|
||||
expect([" test"]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse binary file diff", function() {
|
||||
it("should parse binary file diff", () => {
|
||||
const diff =
|
||||
"diff --git a/last-changes-config.png b/last-changes-config.png\n" +
|
||||
"index 322248b..56fc1f2 100644\n" +
|
||||
|
|
@ -553,19 +553,19 @@ describe("DiffParser", function() {
|
|||
"+++ b/last-changes-config.png\n" +
|
||||
"Binary files differ";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(0).toEqual(file1.addedLines);
|
||||
expect(0).toEqual(file1.deletedLines);
|
||||
expect("last-changes-config.png").toEqual(file1.oldName);
|
||||
expect("last-changes-config.png").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(0).toEqual(file1.blocks[0].lines.length);
|
||||
expect("Binary files differ").toEqual(file1.blocks[0].header);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(0);
|
||||
expect(file1.deletedLines).toEqual(0);
|
||||
expect(file1.oldName).toEqual("last-changes-config.png");
|
||||
expect(file1.newName).toEqual("last-changes-config.png");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||
expect(file1.blocks[0].header).toEqual("Binary files differ");
|
||||
});
|
||||
|
||||
it("should parse diff with --find-renames", function() {
|
||||
it("should parse diff with --find-renames", () => {
|
||||
const diff =
|
||||
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
||||
"similarity index 98%\n" +
|
||||
|
|
@ -581,22 +581,22 @@ describe("DiffParser", function() {
|
|||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
const result = parse(diff);
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("src/test-bar.js").toEqual(file1.oldName);
|
||||
expect("src/test-baz.js").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(5).toEqual(file1.blocks[0].lines.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("src/test-bar.js");
|
||||
expect(file1.newName).toEqual("src/test-baz.js");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]);
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse diff with prefix 2", function() {
|
||||
it("should parse diff with prefix 2", () => {
|
||||
const diff =
|
||||
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
|
||||
"similarity index 88%\n" +
|
||||
|
|
@ -640,36 +640,36 @@ describe("DiffParser", function() {
|
|||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
expect(3).toEqual(result.length);
|
||||
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||
expect(result.length).toEqual(3);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(2).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("Test.scala").toEqual(file1.oldName);
|
||||
expect("ScalaTest.scala").toEqual(file1.newName);
|
||||
expect(2).toEqual(file1.blocks.length);
|
||||
expect(8).toEqual(file1.blocks[0].lines.length);
|
||||
expect(7).toEqual(file1.blocks[1].lines.length);
|
||||
expect(file1.addedLines).toEqual(2);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("Test.scala");
|
||||
expect(file1.newName).toEqual("ScalaTest.scala");
|
||||
expect(file1.blocks.length).toEqual(2);
|
||||
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||
expect(file1.blocks[1].lines.length).toEqual(7);
|
||||
|
||||
const file2 = result[1];
|
||||
expect("/dev/null").toEqual(file2.oldName);
|
||||
expect("tardis.png").toEqual(file2.newName);
|
||||
expect(file2.oldName).toEqual("/dev/null");
|
||||
expect(file2.newName).toEqual("tardis.png");
|
||||
|
||||
const file3 = result[2];
|
||||
expect(1).toEqual(file3.addedLines);
|
||||
expect(1).toEqual(file3.deletedLines);
|
||||
expect("src/test-bar.js").toEqual(file3.oldName);
|
||||
expect("src/test-baz.js").toEqual(file3.newName);
|
||||
expect(1).toEqual(file3.blocks.length);
|
||||
expect(5).toEqual(file3.blocks[0].lines.length);
|
||||
expect(file3.addedLines).toEqual(1);
|
||||
expect(file3.deletedLines).toEqual(1);
|
||||
expect(file3.oldName).toEqual("src/test-bar.js");
|
||||
expect(file3.newName).toEqual("src/test-baz.js");
|
||||
expect(file3.blocks.length).toEqual(1);
|
||||
expect(file3.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file3.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]);
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
|
||||
it("should parse binary with content", function() {
|
||||
it("should parse binary with content", () => {
|
||||
const diff =
|
||||
"diff --git a/favicon.png b/favicon.png\n" +
|
||||
"deleted file mode 100644\n" +
|
||||
|
|
@ -703,26 +703,26 @@ describe("DiffParser", function() {
|
|||
" }\n" +
|
||||
" ";
|
||||
|
||||
const result = DiffParser.generateDiffJson(diff);
|
||||
expect(2).toEqual(result.length);
|
||||
const result = parse(diff);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
const file1 = result[0];
|
||||
expect("favicon.png").toEqual(file1.oldName);
|
||||
expect("favicon.png").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(0).toEqual(file1.blocks[0].lines.length);
|
||||
expect(file1.oldName).toEqual("favicon.png");
|
||||
expect(file1.newName).toEqual("favicon.png");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||
|
||||
const file2 = result[1];
|
||||
expect(1).toEqual(file2.addedLines);
|
||||
expect(1).toEqual(file2.deletedLines);
|
||||
expect("src/test-bar.js").toEqual(file2.oldName);
|
||||
expect("src/test-baz.js").toEqual(file2.newName);
|
||||
expect(1).toEqual(file2.blocks.length);
|
||||
expect(5).toEqual(file2.blocks[0].lines.length);
|
||||
expect(file2.addedLines).toEqual(1);
|
||||
expect(file2.deletedLines).toEqual(1);
|
||||
expect(file2.oldName).toEqual("src/test-bar.js");
|
||||
expect(file2.newName).toEqual("src/test-baz.js");
|
||||
expect(file2.blocks.length).toEqual(1);
|
||||
expect(file2.blocks[0].lines.length).toEqual(5);
|
||||
const linesContent = file2.blocks[0].lines.map(function(line) {
|
||||
return line.content;
|
||||
});
|
||||
expect(linesContent).toEqual([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]);
|
||||
expect([" function foo() {", '-var bar = "Whoops!";', '+var baz = "Whoops!";', " }", " "]).toEqual(linesContent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
const Diff2Html = require("../diff2html.js").Diff2Html;
|
||||
import { parse, html } from "../diff2html";
|
||||
import { DiffFile, LineType } from "../render-utils";
|
||||
|
||||
const diffExample1 =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
|
|
@ -9,27 +10,27 @@ const diffExample1 =
|
|||
"-test\n" +
|
||||
"+test1\n";
|
||||
|
||||
const jsonExample1 = [
|
||||
const jsonExample1: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: "1",
|
||||
oldStartLine2: null,
|
||||
newStartLine: "1",
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
|
|
@ -38,9 +39,10 @@ const jsonExample1 = [
|
|||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: undefined,
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
language: "",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -183,9 +185,9 @@ const htmlSideExample1 =
|
|||
|
||||
const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
|
||||
|
||||
describe("Diff2Html", function() {
|
||||
describe("getJsonFromDiff", function() {
|
||||
it("should parse simple diff to json", function() {
|
||||
describe("Diff2Html", () => {
|
||||
describe("getJsonFromDiff", () => {
|
||||
it("should parse simple diff to json", () => {
|
||||
const diff =
|
||||
"diff --git a/sample b/sample\n" +
|
||||
"index 0000001..0ddf2ba\n" +
|
||||
|
|
@ -194,19 +196,19 @@ describe("Diff2Html", function() {
|
|||
"@@ -1 +1 @@\n" +
|
||||
"-test\n" +
|
||||
"+test1\n";
|
||||
const result = Diff2Html.getJsonFromDiff(diff);
|
||||
const result = parse(diff);
|
||||
|
||||
const file1 = result[0];
|
||||
expect(1).toEqual(result.length);
|
||||
expect(1).toEqual(file1.addedLines);
|
||||
expect(1).toEqual(file1.deletedLines);
|
||||
expect("sample").toEqual(file1.oldName);
|
||||
expect("sample").toEqual(file1.newName);
|
||||
expect(1).toEqual(file1.blocks.length);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(file1.addedLines).toEqual(1);
|
||||
expect(file1.deletedLines).toEqual(1);
|
||||
expect(file1.oldName).toEqual("sample");
|
||||
expect(file1.newName).toEqual("sample");
|
||||
expect(file1.blocks.length).toEqual(1);
|
||||
});
|
||||
|
||||
// Test case for issue #49
|
||||
it("should parse diff with added EOF", function() {
|
||||
it("should parse diff with added EOF", () => {
|
||||
const diff =
|
||||
"diff --git a/sample.scala b/sample.scala\n" +
|
||||
"index b583263..8b2fc3e 100644\n" +
|
||||
|
|
@ -223,67 +225,67 @@ describe("Diff2Html", function() {
|
|||
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
|
||||
"+ PluginError, CodeParserError, EngineError = Value\n" +
|
||||
"+}\n";
|
||||
const result = Diff2Html.getJsonFromDiff(diff);
|
||||
const result = parse(diff);
|
||||
|
||||
expect(50).toEqual(result[0].blocks[0].lines[0].oldNumber);
|
||||
expect(50).toEqual(result[0].blocks[0].lines[0].newNumber);
|
||||
expect(result[0].blocks[0].lines[0].oldNumber).toEqual(50);
|
||||
expect(result[0].blocks[0].lines[0].newNumber).toEqual(50);
|
||||
|
||||
expect(51).toEqual(result[0].blocks[0].lines[1].oldNumber);
|
||||
expect(51).toEqual(result[0].blocks[0].lines[1].newNumber);
|
||||
expect(result[0].blocks[0].lines[1].oldNumber).toEqual(51);
|
||||
expect(result[0].blocks[0].lines[1].newNumber).toEqual(51);
|
||||
|
||||
expect(52).toEqual(result[0].blocks[0].lines[2].oldNumber);
|
||||
expect(52).toEqual(result[0].blocks[0].lines[2].newNumber);
|
||||
expect(result[0].blocks[0].lines[2].oldNumber).toEqual(52);
|
||||
expect(result[0].blocks[0].lines[2].newNumber).toEqual(52);
|
||||
|
||||
expect(53).toEqual(result[0].blocks[0].lines[3].oldNumber);
|
||||
expect(null).toEqual(result[0].blocks[0].lines[3].newNumber);
|
||||
expect(result[0].blocks[0].lines[3].oldNumber).toEqual(53);
|
||||
expect(result[0].blocks[0].lines[3].newNumber).toBeUndefined();
|
||||
|
||||
expect(54).toEqual(result[0].blocks[0].lines[4].oldNumber);
|
||||
expect(null).toEqual(result[0].blocks[0].lines[4].newNumber);
|
||||
expect(result[0].blocks[0].lines[4].oldNumber).toEqual(54);
|
||||
expect(result[0].blocks[0].lines[4].newNumber).toBeUndefined();
|
||||
|
||||
expect(null).toEqual(result[0].blocks[0].lines[5].oldNumber);
|
||||
expect(53).toEqual(result[0].blocks[0].lines[5].newNumber);
|
||||
expect(result[0].blocks[0].lines[5].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[5].newNumber).toEqual(53);
|
||||
|
||||
expect(null).toEqual(result[0].blocks[0].lines[6].oldNumber);
|
||||
expect(54).toEqual(result[0].blocks[0].lines[6].newNumber);
|
||||
expect(result[0].blocks[0].lines[6].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[6].newNumber).toEqual(54);
|
||||
|
||||
expect(null).toEqual(result[0].blocks[0].lines[7].oldNumber);
|
||||
expect(55).toEqual(result[0].blocks[0].lines[7].newNumber);
|
||||
expect(result[0].blocks[0].lines[7].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[7].newNumber).toEqual(55);
|
||||
|
||||
expect(null).toEqual(result[0].blocks[0].lines[8].oldNumber);
|
||||
expect(56).toEqual(result[0].blocks[0].lines[8].newNumber);
|
||||
expect(result[0].blocks[0].lines[8].oldNumber).toBeUndefined();
|
||||
expect(result[0].blocks[0].lines[8].newNumber).toEqual(56);
|
||||
});
|
||||
|
||||
it("should generate pretty line by line html from diff", function() {
|
||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1);
|
||||
expect(htmlLineExample1).toEqual(result);
|
||||
it("should generate pretty line by line html from diff", () => {
|
||||
const result = html(diffExample1);
|
||||
expect(result).toEqual(htmlLineExample1);
|
||||
});
|
||||
|
||||
it("should generate pretty line by line html from json", function() {
|
||||
const result = Diff2Html.getPrettyHtmlFromJson(jsonExample1);
|
||||
expect(htmlLineExample1).toEqual(result);
|
||||
it("should generate pretty line by line html from json", () => {
|
||||
const result = html(jsonExample1);
|
||||
expect(result).toEqual(htmlLineExample1);
|
||||
});
|
||||
|
||||
it("should generate pretty diff with files summary", function() {
|
||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1, { showFiles: true });
|
||||
expect(htmlLineExample1WithFilesSummary).toEqual(result);
|
||||
it("should generate pretty diff with files summary", () => {
|
||||
const result = html(diffExample1, { showFiles: true });
|
||||
expect(result).toEqual(htmlLineExample1WithFilesSummary);
|
||||
});
|
||||
|
||||
it("should generate pretty side by side html from diff", function() {
|
||||
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1);
|
||||
expect(htmlSideExample1).toEqual(result);
|
||||
it("should generate pretty side by side html from diff", () => {
|
||||
const result = html(diffExample1, { outputFormat: "side-by-side" });
|
||||
expect(result).toEqual(htmlSideExample1);
|
||||
});
|
||||
|
||||
it("should generate pretty side by side html from json", function() {
|
||||
const result = Diff2Html.getPrettySideBySideHtmlFromJson(jsonExample1);
|
||||
expect(htmlSideExample1).toEqual(result);
|
||||
it("should generate pretty side by side html from json", () => {
|
||||
const result = html(jsonExample1, { outputFormat: "side-by-side" });
|
||||
expect(result).toEqual(htmlSideExample1);
|
||||
});
|
||||
|
||||
it("should generate pretty side by side html from diff 2", function() {
|
||||
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1, { showFiles: true });
|
||||
expect(htmlSideExample1WithFilesSummary).toEqual(result);
|
||||
it("should generate pretty side by side html from diff 2", () => {
|
||||
const result = html(diffExample1, { outputFormat: "side-by-side", showFiles: true });
|
||||
expect(result).toEqual(htmlSideExample1WithFilesSummary);
|
||||
});
|
||||
|
||||
it("should generate pretty side by side html from diff with html on headers", function() {
|
||||
it("should generate pretty side by side html from diff with html on headers", () => {
|
||||
const diffExample2 =
|
||||
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
|
||||
"index fc3e3f4..b486d10 100644\n" +
|
||||
|
|
@ -516,8 +518,8 @@ describe("Diff2Html", function() {
|
|||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample2);
|
||||
expect(result).toEqual(htmlExample2);
|
||||
const result = html(diffExample2);
|
||||
expect(htmlExample2).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,17 +1,30 @@
|
|||
const FileListPrinter = require("../file-list-printer.js").FileListPrinter;
|
||||
import { render } from "../file-list-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
|
||||
describe("FileListPrinter", function() {
|
||||
describe("generateFileList", function() {
|
||||
it("should expose old and new files to templates", function() {
|
||||
describe("FileListPrinter", () => {
|
||||
describe("generateFileList", () => {
|
||||
it("should expose old and new files to templates", () => {
|
||||
const hoganUtils = new HoganJsUtils({
|
||||
rawTemplates: {
|
||||
"file-summary-wrapper": "{{{files}}}",
|
||||
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
|
||||
}
|
||||
});
|
||||
const files = [
|
||||
{
|
||||
addedlines: 12,
|
||||
deletedlines: 41,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "my/file/name.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
|
|
@ -19,6 +32,9 @@ describe("FileListPrinter", function() {
|
|||
newName: "my/file/name2.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
|
|
@ -27,6 +43,9 @@ describe("FileListPrinter", function() {
|
|||
isNew: true
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
|
|
@ -36,26 +55,23 @@ describe("FileListPrinter", function() {
|
|||
}
|
||||
];
|
||||
|
||||
const fileListPrinter = new FileListPrinter({
|
||||
rawTemplates: {
|
||||
"file-summary-wrapper": "{{{files}}}",
|
||||
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
|
||||
}
|
||||
});
|
||||
|
||||
const fileHtml = fileListPrinter.generateFileList(files);
|
||||
const fileHtml = render(files, hoganUtils);
|
||||
const expected =
|
||||
"my/file/name.js, my/file/name.js, my/file/name.js\n" +
|
||||
"my/file/name1.js, my/file/name2.js, my/file/{name1.js → name2.js}\n" +
|
||||
"dev/null, my/file/name.js, my/file/name.js\n" +
|
||||
"my/file/name.js, dev/null, my/file/name.js";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for all kinds of files", function() {
|
||||
it("should work for all kinds of files", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const files = [
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
|
|
@ -63,6 +79,9 @@ describe("FileListPrinter", function() {
|
|||
newName: "my/file/name.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
|
|
@ -70,6 +89,9 @@ describe("FileListPrinter", function() {
|
|||
newName: "my/file/name2.js"
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 12,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
|
|
@ -78,6 +100,9 @@ describe("FileListPrinter", function() {
|
|||
isNew: true
|
||||
},
|
||||
{
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: [],
|
||||
addedLines: 0,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
|
|
@ -87,8 +112,7 @@ describe("FileListPrinter", function() {
|
|||
}
|
||||
];
|
||||
|
||||
const fileListPrinter = new FileListPrinter();
|
||||
const fileHtml = fileListPrinter.generateFileList(files);
|
||||
const fileHtml = render(files, hoganUtils);
|
||||
|
||||
const expected =
|
||||
'<div class="d2h-file-list-wrapper">\n' +
|
||||
|
|
@ -149,7 +173,7 @@ describe("FileListPrinter", function() {
|
|||
" </ol>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
const HoganJsUtils = new (require("../hoganjs-utils.js")).HoganJsUtils();
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
|
||||
describe("HoganJsUtils", function() {
|
||||
describe("render", function() {
|
||||
const emptyDiffHtml =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
it("should render view", function() {
|
||||
const result = HoganJsUtils.render("generic", "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
diffParser: diffParser
|
||||
});
|
||||
expect(emptyDiffHtml).toEqual(result);
|
||||
});
|
||||
|
||||
it("should render view without cache", function() {
|
||||
const result = HoganJsUtils.render(
|
||||
"generic",
|
||||
"empty-diff",
|
||||
{
|
||||
contentClass: "d2h-code-line",
|
||||
diffParser: diffParser
|
||||
},
|
||||
{ noCache: true }
|
||||
);
|
||||
expect(emptyDiffHtml).toEqual(result);
|
||||
});
|
||||
|
||||
it("should return null if template is missing", function() {
|
||||
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils({ noCache: true });
|
||||
const result = hoganUtils.render("generic", "missing-template", {});
|
||||
expect(null).toEqual(result);
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with compiled templates", function() {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
|
||||
|
||||
const config = { templates: { "generic-empty-diff": emptyDiffTemplate } };
|
||||
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
|
||||
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with uncompiled templates", function() {
|
||||
const emptyDiffTemplate = "<p>{{myName}}</p>";
|
||||
|
||||
const config = { rawTemplates: { "generic-empty-diff": emptyDiffTemplate } };
|
||||
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
|
||||
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden giving priority to compiled templates", function() {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
|
||||
const emptyDiffTemplateUncompiled = "<p>Not used!</p>";
|
||||
|
||||
const config = {
|
||||
templates: { "generic-empty-diff": emptyDiffTemplate },
|
||||
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled }
|
||||
};
|
||||
const hoganUtils = new (require("../hoganjs-utils.js")).HoganJsUtils(config);
|
||||
const result = hoganUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect("<p>Rodrigo Fernandes</p>").toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
src/__tests__/hogan-cache-tests.ts
Normal file
66
src/__tests__/hogan-cache-tests.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { CSSLineClass } from "../render-utils";
|
||||
|
||||
describe("HoganJsUtils", () => {
|
||||
describe("render", () => {
|
||||
const emptyDiffHtml =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
' <div class="d2h-code-line d2h-info">\n' +
|
||||
" File without changes\n" +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
it("should render view", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: CSSLineClass
|
||||
});
|
||||
expect(result).toEqual(emptyDiffHtml);
|
||||
});
|
||||
|
||||
it("should render view without cache", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: CSSLineClass
|
||||
});
|
||||
expect(result).toEqual(emptyDiffHtml);
|
||||
});
|
||||
|
||||
it("should throw exception if template is missing", () => {
|
||||
const hoganJsUtils = new HoganJsUtils({});
|
||||
expect(() => hoganJsUtils.render("generic", "missing-template", {})).toThrow(Error);
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with compiled templates", () => {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>{{myName}}</p>");
|
||||
const hoganJsUtils = new HoganJsUtils({ compiledTemplates: { "generic-empty-diff": emptyDiffTemplate } });
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden with uncompiled templates", () => {
|
||||
const emptyDiffTemplate = "<p>{{myName}}</p>";
|
||||
const hoganJsUtils = new HoganJsUtils({ rawTemplates: { "generic-empty-diff": emptyDiffTemplate } });
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
|
||||
it("should allow templates to be overridden giving priority to raw templates", () => {
|
||||
const emptyDiffTemplate = HoganJsUtils.compile("<p>Not used!</p>");
|
||||
const emptyDiffTemplateUncompiled = "<p>{{myName}}</p>";
|
||||
const hoganJsUtils = new HoganJsUtils({
|
||||
compiledTemplates: { "generic-empty-diff": emptyDiffTemplate },
|
||||
rawTemplates: { "generic-empty-diff": emptyDiffTemplateUncompiled }
|
||||
});
|
||||
|
||||
const result = hoganJsUtils.render("generic", "empty-diff", { myName: "Rodrigo Fernandes" });
|
||||
expect(result).toEqual("<p>Rodrigo Fernandes</p>");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
const LineByLinePrinter = require("../line-by-line-printer.js").LineByLinePrinter;
|
||||
import LineByLineRenderer from "../line-by-line-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
|
||||
|
||||
describe("LineByLinePrinter", function() {
|
||||
describe("_generateEmptyDiff", function() {
|
||||
it("should return an empty diff", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
const fileHtml = lineByLinePrinter._generateEmptyDiff();
|
||||
describe("LineByLineRenderer", () => {
|
||||
describe("_generateEmptyDiff", () => {
|
||||
it("should return an empty diff", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const fileHtml = lineByLineRenderer.generateEmptyDiff();
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-info">\n' +
|
||||
|
|
@ -14,15 +17,15 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeLineHtml", function() {
|
||||
it("should work for insertions", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "test", "+");
|
||||
describe("makeLineHtml", () => {
|
||||
it("should work for insertions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "test", undefined, 30, "+");
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -38,13 +41,13 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for deletions", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "", "test", "-");
|
||||
it("should work for deletions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.DELETES, "test", 30, undefined, "-");
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -60,13 +63,13 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should convert indents into non breakin spaces (2 white spaces)", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+");
|
||||
it("should convert indents into non breakin spaces (2 white spaces)", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -82,13 +85,13 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should convert indents into non breakin spaces (4 white spaces)", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+");
|
||||
it("should convert indents into non breakin spaces (4 white spaces)", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -104,13 +107,13 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should preserve tabs", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "\ttest", "+");
|
||||
it("should preserve tabs", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "\ttest", undefined, 30, "+");
|
||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -127,24 +130,28 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeFileDiffHtml", function() {
|
||||
it("should work for simple file", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
describe("makeFileDiffHtml", () => {
|
||||
it("should work for simple file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
deletedLines: 41,
|
||||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "my/file/name.js"
|
||||
newName: "my/file/name.js",
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -166,10 +173,11 @@ describe("LineByLinePrinter", function() {
|
|||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple added file", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
it("should work for simple added file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
|
|
@ -177,11 +185,14 @@ describe("LineByLinePrinter", function() {
|
|||
language: "js",
|
||||
oldName: "dev/null",
|
||||
newName: "my/file/name.js",
|
||||
isNew: true
|
||||
isNew: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -203,10 +214,11 @@ describe("LineByLinePrinter", function() {
|
|||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple deleted file", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
it("should work for simple deleted file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 0,
|
||||
|
|
@ -214,11 +226,14 @@ describe("LineByLinePrinter", function() {
|
|||
language: "js",
|
||||
oldName: "my/file/name.js",
|
||||
newName: "dev/null",
|
||||
isDeleted: true
|
||||
isDeleted: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -240,10 +255,11 @@ describe("LineByLinePrinter", function() {
|
|||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for simple renamed file", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
it("should work for simple renamed file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
addedLines: 12,
|
||||
|
|
@ -251,11 +267,14 @@ describe("LineByLinePrinter", function() {
|
|||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js",
|
||||
isRename: true
|
||||
isRename: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected =
|
||||
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -277,61 +296,71 @@ describe("LineByLinePrinter", function() {
|
|||
" </div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({
|
||||
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
renderNothingWhenEmpty: true
|
||||
});
|
||||
|
||||
const file = {
|
||||
addedLines: 0,
|
||||
deletedLines: 0,
|
||||
language: "js",
|
||||
oldName: "my/file/name1.js",
|
||||
newName: "my/file/name2.js",
|
||||
isRename: true,
|
||||
isCombined: false,
|
||||
isGitDiff: false,
|
||||
blocks: []
|
||||
};
|
||||
|
||||
const diffs = "<span>Random Html</span>";
|
||||
|
||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
||||
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||
|
||||
const expected = "";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeLineByLineHtmlWrapper", function() {
|
||||
it("should work for simple content", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
const fileHtml = lineByLinePrinter.makeLineByLineHtmlWrapper("<span>Random Html</span>");
|
||||
describe("makeLineByLineHtmlWrapper", () => {
|
||||
it("should work for simple content", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const fileHtml = lineByLineRenderer.makeLineByLineHtmlWrapper("<span>Random Html</span>");
|
||||
|
||||
const expected = '<div class="d2h-wrapper">\n' + " <span>Random Html</span>\n" + "</div>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateLineByLineJsonHtml", function() {
|
||||
it("should work for list of files", function() {
|
||||
const exampleJson = [
|
||||
describe("generateLineByLineJsonHtml", () => {
|
||||
it("should work for list of files", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: "1",
|
||||
oldStartLine2: null,
|
||||
newStartLine: "1",
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
|
|
@ -340,17 +369,21 @@ describe("LineByLinePrinter", function() {
|
|||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: undefined,
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
language: "txt",
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
const lineByLinePrinter = new LineByLinePrinter({ matching: "lines" });
|
||||
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
matching: "lines"
|
||||
});
|
||||
const html = lineByLineRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
|
|
@ -368,23 +401,23 @@ describe("LineByLinePrinter", function() {
|
|||
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-del">\n' +
|
||||
' <td class="d2h-code-linenumber d2h-del d2h-change">\n' +
|
||||
' <div class="line-num1">1</div>\n' +
|
||||
'<div class="line-num2"></div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-line d2h-del">\n' +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-linenumber d2h-ins">\n' +
|
||||
' <td class="d2h-code-linenumber d2h-ins d2h-change">\n' +
|
||||
' <div class="line-num1"></div>\n' +
|
||||
'<div class="line-num2">1</div>\n' +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-line d2h-ins">\n' +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
|
|
@ -397,10 +430,10 @@ describe("LineByLinePrinter", function() {
|
|||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
|
||||
it("should work for empty blocks", function() {
|
||||
it("should work for empty blocks", () => {
|
||||
const exampleJson = [
|
||||
{
|
||||
blocks: [],
|
||||
|
|
@ -409,12 +442,16 @@ describe("LineByLinePrinter", function() {
|
|||
oldName: "sample",
|
||||
language: "js",
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
isCombined: false,
|
||||
isGitDiff: false
|
||||
}
|
||||
];
|
||||
|
||||
const lineByLinePrinter = new LineByLinePrinter({ renderNothingWhenEmpty: false });
|
||||
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||
renderNothingWhenEmpty: false
|
||||
});
|
||||
const html = lineByLineRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -443,31 +480,32 @@ describe("LineByLinePrinter", function() {
|
|||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_processLines", function() {
|
||||
it("should work for simple block header", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
const oldLines = [
|
||||
describe("_processLines", () => {
|
||||
it("should work for simple block header", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const oldLines: DiffLine[] = [
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
}
|
||||
];
|
||||
const newLines = [
|
||||
const newLines: DiffLine[] = [
|
||||
{
|
||||
content: "+test1r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
];
|
||||
|
||||
const html = lineByLinePrinter._processLines(false, oldLines, newLines);
|
||||
const html = lineByLineRenderer.processLines(false, oldLines, newLines);
|
||||
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -494,45 +532,46 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_generateFileHtml", function() {
|
||||
it("should work for simple file", function() {
|
||||
const lineByLinePrinter = new LineByLinePrinter({});
|
||||
const file = {
|
||||
describe("_generateFileHtml", () => {
|
||||
it("should work for simple file", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||
const file: DiffFile = {
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: " one context line",
|
||||
type: "d2h-cntx",
|
||||
type: LineType.CONTEXT,
|
||||
oldNumber: 1,
|
||||
newNumber: 1
|
||||
},
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 2,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 2
|
||||
},
|
||||
{
|
||||
content: "+test2r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 3
|
||||
}
|
||||
],
|
||||
oldStartLine: "1",
|
||||
oldStartLine2: null,
|
||||
newStartLine: "1",
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
|
|
@ -541,12 +580,13 @@ describe("LineByLinePrinter", function() {
|
|||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: undefined,
|
||||
language: "txt",
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
};
|
||||
|
||||
const html = lineByLinePrinter._generateFileHtml(file);
|
||||
const html = lineByLineRenderer.generateFileHtml(file);
|
||||
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
|
|
@ -600,7 +640,7 @@ describe("LineByLinePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
const PrinterUtils = require("../printer-utils.js").PrinterUtils;
|
||||
import * as renderUtils from "../render-utils";
|
||||
|
||||
describe("Utils", function() {
|
||||
describe("getHtmlId", function() {
|
||||
it("should generate file unique id", function() {
|
||||
const result = PrinterUtils.getHtmlId({
|
||||
const result = renderUtils.getHtmlId({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
expect("d2h-960013").toEqual(result);
|
||||
});
|
||||
it("should generate file unique id for empty hashes", function() {
|
||||
const result = PrinterUtils.getHtmlId({
|
||||
const result = renderUtils.getHtmlId({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
|
|
@ -20,105 +20,102 @@ describe("Utils", function() {
|
|||
|
||||
describe("getDiffName", function() {
|
||||
it("should generate the file name for a changed file", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "sample.js",
|
||||
newName: "sample.js"
|
||||
});
|
||||
expect("sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and full rename", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "sample1.js",
|
||||
newName: "sample2.js"
|
||||
});
|
||||
expect("sample1.js → sample2.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and prefix rename", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "src/path/sample.js",
|
||||
newName: "source/path/sample.js"
|
||||
});
|
||||
expect("{src → source}/path/sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and suffix rename", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "src/path/sample1.js",
|
||||
newName: "src/path/sample2.js"
|
||||
});
|
||||
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a changed file and middle rename", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "src/really/big/path/sample.js",
|
||||
newName: "src/small/path/sample.js"
|
||||
});
|
||||
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a deleted file", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "src/my/file.js",
|
||||
newName: "/dev/null"
|
||||
});
|
||||
expect("src/my/file.js").toEqual(result);
|
||||
});
|
||||
it("should generate the file name for a new file", function() {
|
||||
const result = PrinterUtils.getDiffName({
|
||||
const result = renderUtils.filenameDiff({
|
||||
oldName: "/dev/null",
|
||||
newName: "src/my/file.js"
|
||||
});
|
||||
expect("src/my/file.js").toEqual(result);
|
||||
});
|
||||
it("should generate handle undefined filename", function() {
|
||||
const result = PrinterUtils.getDiffName({});
|
||||
expect("unknown/file/path").toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("diffHighlight", function() {
|
||||
it("should highlight two lines", function() {
|
||||
const result = PrinterUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", { matching: "words" });
|
||||
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, {
|
||||
matching: "words"
|
||||
});
|
||||
|
||||
expect({
|
||||
first: {
|
||||
expect(result).toEqual({
|
||||
oldLine: {
|
||||
prefix: "-",
|
||||
line: "var <del>myVar</del> = <del>2</del>;"
|
||||
content: "var <del>myVar</del> = <del>2</del>;"
|
||||
},
|
||||
second: {
|
||||
newLine: {
|
||||
prefix: "+",
|
||||
line: "var <ins>myVariable</ins> = <ins>3</ins>;"
|
||||
content: "var <ins>myVariable</ins> = <ins>3</ins>;"
|
||||
}
|
||||
}).toEqual(result);
|
||||
});
|
||||
});
|
||||
it("should highlight two lines char by char", function() {
|
||||
const result = PrinterUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", { diffStyle: "char" });
|
||||
const result = renderUtils.diffHighlight("-var myVar = 2;", "+var myVariable = 3;", false, { diffStyle: "char" });
|
||||
|
||||
expect({
|
||||
first: {
|
||||
oldLine: {
|
||||
prefix: "-",
|
||||
line: "var myVar = <del>2</del>;"
|
||||
content: "var myVar = <del>2</del>;"
|
||||
},
|
||||
second: {
|
||||
newLine: {
|
||||
prefix: "+",
|
||||
line: "var myVar<ins>iable</ins> = <ins>3</ins>;"
|
||||
content: "var myVar<ins>iable</ins> = <ins>3</ins>;"
|
||||
}
|
||||
}).toEqual(result);
|
||||
});
|
||||
it("should highlight combined diff lines", function() {
|
||||
const result = PrinterUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", {
|
||||
const result = renderUtils.diffHighlight(" -var myVar = 2;", " +var myVariable = 3;", true, {
|
||||
diffStyle: "word",
|
||||
isCombined: true,
|
||||
matching: "words",
|
||||
matchWordsThreshold: 1.0
|
||||
});
|
||||
|
||||
expect({
|
||||
first: {
|
||||
oldLine: {
|
||||
prefix: " -",
|
||||
line: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
|
||||
content: 'var <del class="d2h-change">myVar</del> = <del class="d2h-change">2</del>;'
|
||||
},
|
||||
second: {
|
||||
newLine: {
|
||||
prefix: " +",
|
||||
line: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
|
||||
content: 'var <ins class="d2h-change">myVariable</ins> = <ins class="d2h-change">3</ins>;'
|
||||
}
|
||||
}).toEqual(result);
|
||||
});
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
const SideBySidePrinter = require("../side-by-side-printer.js").SideBySidePrinter;
|
||||
import SideBySideRenderer from "../side-by-side-renderer";
|
||||
import HoganJsUtils from "../hoganjs-utils";
|
||||
import { LineType, CSSLineClass, DiffLine, DiffFile } from "../render-utils";
|
||||
|
||||
describe("SideBySidePrinter", function() {
|
||||
describe("generateEmptyDiff", function() {
|
||||
it("should return an empty diff", function() {
|
||||
const sideBySidePrinter = new SideBySidePrinter({});
|
||||
const fileHtml = sideBySidePrinter.generateEmptyDiff();
|
||||
describe("SideBySideRenderer", () => {
|
||||
describe("generateEmptyDiff", () => {
|
||||
it("should return an empty diff", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateEmptyDiff();
|
||||
const expectedRight = "";
|
||||
const expectedLeft =
|
||||
"<tr>\n" +
|
||||
|
|
@ -15,51 +18,53 @@ describe("SideBySidePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expectedRight).toEqual(fileHtml.right);
|
||||
expect(expectedLeft).toEqual(fileHtml.left);
|
||||
expect(fileHtml.right).toEqual(expectedRight);
|
||||
expect(fileHtml.left).toEqual(expectedLeft);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSideBySideFileHtml", function() {
|
||||
it("should generate lines with the right prefixes", function() {
|
||||
const sideBySidePrinter = new SideBySidePrinter({});
|
||||
describe("generateSideBySideFileHtml", () => {
|
||||
it("should generate lines with the right prefixes", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
|
||||
const file = {
|
||||
const file: DiffFile = {
|
||||
isGitDiff: true,
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: " context",
|
||||
type: "d2h-cntx",
|
||||
type: LineType.CONTEXT,
|
||||
oldNumber: 19,
|
||||
newNumber: 19
|
||||
},
|
||||
{
|
||||
content: "-removed",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 20,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+added",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 20
|
||||
},
|
||||
{
|
||||
content: "+another added",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 21
|
||||
}
|
||||
],
|
||||
oldStartLine: "19",
|
||||
newStartLine: "19",
|
||||
oldStartLine: 19,
|
||||
newStartLine: 19,
|
||||
header: "@@ -19,7 +19,7 @@"
|
||||
}
|
||||
],
|
||||
deletedLines: 1,
|
||||
addedLines: 1,
|
||||
addedLines: 2,
|
||||
checksumBefore: "fc56817",
|
||||
checksumAfter: "e8e7e49",
|
||||
mode: "100644",
|
||||
|
|
@ -69,7 +74,7 @@ describe("SideBySidePrinter", function() {
|
|||
isCombined: false
|
||||
};
|
||||
|
||||
const fileHtml = sideBySidePrinter.generateSideBySideFileHtml(file);
|
||||
const fileHtml = sideBySideRenderer.generateSideBySideFileHtml(file);
|
||||
|
||||
const expectedLeft =
|
||||
"<tr>\n" +
|
||||
|
|
@ -148,16 +153,16 @@ describe("SideBySidePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expectedLeft).toEqual(fileHtml.left);
|
||||
expect(expectedRight).toEqual(fileHtml.right);
|
||||
expect(fileHtml.left).toEqual(expectedLeft);
|
||||
expect(fileHtml.right).toEqual(expectedRight);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSingleLineHtml", function() {
|
||||
it("should work for insertions", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const sideBySidePrinter = new SideBySidePrinter({});
|
||||
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.INSERTS, 30, "test", "+");
|
||||
describe("generateSingleLineHtml", () => {
|
||||
it("should work for insertions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.INSERTS, "test", 30, "+");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
|
|
@ -171,12 +176,12 @@ describe("SideBySidePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
it("should work for deletions", function() {
|
||||
const diffParser = require("../diff-parser.js").DiffParser;
|
||||
const sideBySidePrinter = new SideBySidePrinter({});
|
||||
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "test", "-");
|
||||
it("should work for deletions", () => {
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.DELETES, "test", 30, "-");
|
||||
const expected =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
|
|
@ -190,33 +195,33 @@ describe("SideBySidePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expected).toEqual(fileHtml);
|
||||
expect(fileHtml).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSideBySideJsonHtml", function() {
|
||||
it("should work for list of files", function() {
|
||||
const exampleJson = [
|
||||
describe("generateSideBySideJsonHtml", () => {
|
||||
it("should work for list of files", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
lines: [
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
},
|
||||
{
|
||||
content: "+test1r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
],
|
||||
oldStartLine: "1",
|
||||
oldStartLine2: null,
|
||||
newStartLine: "1",
|
||||
oldStartLine: 1,
|
||||
oldStartLine2: undefined,
|
||||
newStartLine: 1,
|
||||
header: "@@ -1 +1 @@"
|
||||
}
|
||||
],
|
||||
|
|
@ -225,17 +230,19 @@ describe("SideBySidePrinter", function() {
|
|||
checksumBefore: "0000001",
|
||||
checksumAfter: "0ddf2ba",
|
||||
oldName: "sample",
|
||||
language: undefined,
|
||||
language: "txt",
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
isCombined: false,
|
||||
isGitDiff: true
|
||||
}
|
||||
];
|
||||
|
||||
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" });
|
||||
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
|
||||
const html = sideBySideRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="txt">\n' +
|
||||
' <div class="d2h-file-header">\n' +
|
||||
' <span class="d2h-file-name-wrapper">\n' +
|
||||
' <svg aria-hidden="true" class="d2h-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12">\n' +
|
||||
|
|
@ -254,11 +261,11 @@ describe("SideBySidePrinter", function() {
|
|||
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-del">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||
' <td class="d2h-del d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||
" </div>\n" +
|
||||
|
|
@ -278,11 +285,11 @@ describe("SideBySidePrinter", function() {
|
|||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||
" </td>\n" +
|
||||
"</tr><tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||
" 1\n" +
|
||||
" </td>\n" +
|
||||
' <td class="d2h-ins">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||
' <td class="d2h-ins d2h-change">\n' +
|
||||
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||
" </div>\n" +
|
||||
|
|
@ -296,21 +303,25 @@ describe("SideBySidePrinter", function() {
|
|||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
it("should work for files without blocks", function() {
|
||||
const exampleJson = [
|
||||
it("should work for files without blocks", () => {
|
||||
const exampleJson: DiffFile[] = [
|
||||
{
|
||||
blocks: [],
|
||||
oldName: "sample",
|
||||
language: "js",
|
||||
newName: "sample",
|
||||
isCombined: false
|
||||
isCombined: false,
|
||||
addedLines: 0,
|
||||
deletedLines: 0,
|
||||
isGitDiff: false
|
||||
}
|
||||
];
|
||||
|
||||
const sideBySidePrinter = new SideBySidePrinter();
|
||||
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||
const html = sideBySideRenderer.render(exampleJson);
|
||||
const expected =
|
||||
'<div class="d2h-wrapper">\n' +
|
||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||
|
|
@ -350,32 +361,33 @@ describe("SideBySidePrinter", function() {
|
|||
"</div>\n" +
|
||||
"</div>";
|
||||
|
||||
expect(expected).toEqual(html);
|
||||
expect(html).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("processLines", function() {
|
||||
it("should process file lines", function() {
|
||||
const oldLines = [
|
||||
describe("processLines", () => {
|
||||
it("should process file lines", () => {
|
||||
const oldLines: DiffLine[] = [
|
||||
{
|
||||
content: "-test",
|
||||
type: "d2h-del",
|
||||
type: LineType.DELETE,
|
||||
oldNumber: 1,
|
||||
newNumber: null
|
||||
newNumber: undefined
|
||||
}
|
||||
];
|
||||
|
||||
const newLines = [
|
||||
const newLines: DiffLine[] = [
|
||||
{
|
||||
content: "+test1r",
|
||||
type: "d2h-ins",
|
||||
oldNumber: null,
|
||||
type: LineType.INSERT,
|
||||
oldNumber: undefined,
|
||||
newNumber: 1
|
||||
}
|
||||
];
|
||||
|
||||
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" });
|
||||
const html = sideBySidePrinter.processLines(false, oldLines, newLines);
|
||||
const hoganUtils = new HoganJsUtils({});
|
||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
|
||||
const html = sideBySideRenderer.processLines(false, oldLines, newLines);
|
||||
const expectedLeft =
|
||||
"<tr>\n" +
|
||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||
|
|
@ -402,8 +414,8 @@ describe("SideBySidePrinter", function() {
|
|||
" </td>\n" +
|
||||
"</tr>";
|
||||
|
||||
expect(expectedLeft).toEqual(html.left);
|
||||
expect(expectedRight).toEqual(html.right);
|
||||
expect(html.left).toEqual(expectedLeft);
|
||||
expect(html.right).toEqual(expectedRight);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
const Utils = require("../utils.js").Utils;
|
||||
import { escapeForHtml } from "../utils";
|
||||
|
||||
describe("Utils", function() {
|
||||
describe("escape", function() {
|
||||
it("should escape & with &", function() {
|
||||
const result = Utils.escape("&");
|
||||
const result = escapeForHtml("&");
|
||||
expect("&").toEqual(result);
|
||||
});
|
||||
it("should escape < with <", function() {
|
||||
const result = Utils.escape("<");
|
||||
const result = escapeForHtml("<");
|
||||
expect("<").toEqual(result);
|
||||
});
|
||||
it("should escape > with >", function() {
|
||||
const result = Utils.escape(">");
|
||||
const result = escapeForHtml(">");
|
||||
expect(">").toEqual(result);
|
||||
});
|
||||
it("should escape a string with multiple problematic characters", function() {
|
||||
const result = Utils.escape('<a href="#">\tlink text</a>');
|
||||
const result = escapeForHtml('<a href="#">\tlink text</a>');
|
||||
const expected = "<a href="#">\tlink text</a>";
|
||||
expect(expected).toEqual(result);
|
||||
});
|
||||
|
|
@ -1,448 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff Parser (diff-parser.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const utils = require("./utils.js").Utils;
|
||||
|
||||
const LINE_TYPE = {
|
||||
INSERTS: "d2h-ins",
|
||||
DELETES: "d2h-del",
|
||||
INSERT_CHANGES: "d2h-ins d2h-change",
|
||||
DELETE_CHANGES: "d2h-del d2h-change",
|
||||
CONTEXT: "d2h-cntx",
|
||||
INFO: "d2h-info"
|
||||
};
|
||||
|
||||
function DiffParser() {}
|
||||
|
||||
DiffParser.prototype.LINE_TYPE = LINE_TYPE;
|
||||
|
||||
DiffParser.prototype.generateDiffJson = function(diffInput, configuration) {
|
||||
const config = configuration || {};
|
||||
|
||||
const files = [];
|
||||
let currentFile = null;
|
||||
let currentBlock = null;
|
||||
let oldLine = null;
|
||||
let oldLine2 = null; // Used for combined diff
|
||||
let newLine = null;
|
||||
|
||||
let possibleOldName;
|
||||
let possibleNewName;
|
||||
|
||||
/* Diff Header */
|
||||
const oldFileNameHeader = "--- ";
|
||||
const newFileNameHeader = "+++ ";
|
||||
const hunkHeaderPrefix = "@@";
|
||||
|
||||
/* Add previous block(if exists) before start a new file */
|
||||
function saveBlock() {
|
||||
if (currentBlock) {
|
||||
currentFile.blocks.push(currentBlock);
|
||||
currentBlock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add previous file(if exists) before start a new one
|
||||
* if it has name (to avoid binary files errors)
|
||||
*/
|
||||
function saveFile() {
|
||||
if (currentFile) {
|
||||
if (!currentFile.oldName) {
|
||||
currentFile.oldName = possibleOldName;
|
||||
}
|
||||
|
||||
if (!currentFile.newName) {
|
||||
currentFile.newName = possibleNewName;
|
||||
}
|
||||
|
||||
if (currentFile.newName) {
|
||||
files.push(currentFile);
|
||||
currentFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
possibleOldName = undefined;
|
||||
possibleNewName = undefined;
|
||||
}
|
||||
|
||||
/* Create file structure */
|
||||
function startFile() {
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
currentFile = {};
|
||||
currentFile.blocks = [];
|
||||
currentFile.deletedLines = 0;
|
||||
currentFile.addedLines = 0;
|
||||
}
|
||||
|
||||
function startBlock(line) {
|
||||
saveBlock();
|
||||
|
||||
let values;
|
||||
|
||||
/**
|
||||
* From Range:
|
||||
* -<start line>[,<number of lines>]
|
||||
*
|
||||
* To Range:
|
||||
* +<start line>[,<number of lines>]
|
||||
*
|
||||
* @@ from-file-range to-file-range @@
|
||||
*
|
||||
* @@@ from-file-range from-file-range to-file-range @@@
|
||||
*
|
||||
* number of lines is optional, if omited consider 0
|
||||
*/
|
||||
|
||||
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
|
||||
currentFile.isCombined = false;
|
||||
oldLine = values[1];
|
||||
newLine = values[2];
|
||||
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
|
||||
currentFile.isCombined = true;
|
||||
oldLine = values[1];
|
||||
oldLine2 = values[2];
|
||||
newLine = values[3];
|
||||
} else {
|
||||
if (utils.startsWith(line, hunkHeaderPrefix)) {
|
||||
console.error("Failed to parse lines, starting in 0!");
|
||||
}
|
||||
|
||||
oldLine = 0;
|
||||
newLine = 0;
|
||||
currentFile.isCombined = false;
|
||||
}
|
||||
|
||||
/* Create block metadata */
|
||||
currentBlock = {};
|
||||
currentBlock.lines = [];
|
||||
currentBlock.oldStartLine = oldLine;
|
||||
currentBlock.oldStartLine2 = oldLine2;
|
||||
currentBlock.newStartLine = newLine;
|
||||
currentBlock.header = line;
|
||||
}
|
||||
|
||||
function createLine(line) {
|
||||
const currentLine = {};
|
||||
currentLine.content = line;
|
||||
|
||||
const newLinePrefixes = !currentFile.isCombined ? ["+"] : ["+", " +"];
|
||||
const delLinePrefixes = !currentFile.isCombined ? ["-"] : ["-", " -"];
|
||||
|
||||
/* Fill the line data */
|
||||
if (utils.startsWith(line, newLinePrefixes)) {
|
||||
currentFile.addedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.INSERTS;
|
||||
currentLine.oldNumber = null;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
} else if (utils.startsWith(line, delLinePrefixes)) {
|
||||
currentFile.deletedLines++;
|
||||
|
||||
currentLine.type = LINE_TYPE.DELETES;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = null;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
} else {
|
||||
currentLine.type = LINE_TYPE.CONTEXT;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = newLine++;
|
||||
|
||||
currentBlock.lines.push(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if there is a hunk header coming before a new file starts
|
||||
*
|
||||
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
|
||||
*/
|
||||
function existHunkHeader(line, lineIdx) {
|
||||
let idx = lineIdx;
|
||||
|
||||
while (idx < diffLines.length - 3) {
|
||||
if (utils.startsWith(line, "diff")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
utils.startsWith(diffLines[idx], oldFileNameHeader) &&
|
||||
utils.startsWith(diffLines[idx + 1], newFileNameHeader) &&
|
||||
utils.startsWith(diffLines[idx + 2], hunkHeaderPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var diffLines = diffInput
|
||||
.replace(/\\ No newline at end of file/g, "")
|
||||
.replace(/\r\n?/g, "\n")
|
||||
.split("\n");
|
||||
|
||||
/* Diff */
|
||||
const oldMode = /^old mode (\d{6})/;
|
||||
const newMode = /^new mode (\d{6})/;
|
||||
const deletedFileMode = /^deleted file mode (\d{6})/;
|
||||
const newFileMode = /^new file mode (\d{6})/;
|
||||
|
||||
const copyFrom = /^copy from "?(.+)"?/;
|
||||
const copyTo = /^copy to "?(.+)"?/;
|
||||
|
||||
const renameFrom = /^rename from "?(.+)"?/;
|
||||
const renameTo = /^rename to "?(.+)"?/;
|
||||
|
||||
const similarityIndex = /^similarity index (\d+)%/;
|
||||
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
|
||||
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
|
||||
|
||||
const binaryFiles = /^Binary files (.*) and (.*) differ/;
|
||||
const binaryDiff = /^GIT binary patch/;
|
||||
|
||||
/* Combined Diff */
|
||||
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
|
||||
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
|
||||
const combinedNewFile = /^new file mode (\d{6})/;
|
||||
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
|
||||
|
||||
diffLines.forEach(function(line, lineIndex) {
|
||||
// Unmerged paths, and possibly other non-diffable files
|
||||
// https://github.com/scottgonzalez/pretty-diff/issues/11
|
||||
// Also, remove some useless lines
|
||||
if (!line || utils.startsWith(line, "*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used to store regex capture groups
|
||||
let values;
|
||||
|
||||
const prevLine = diffLines[lineIndex - 1];
|
||||
const nxtLine = diffLines[lineIndex + 1];
|
||||
const afterNxtLine = diffLines[lineIndex + 2];
|
||||
|
||||
if (utils.startsWith(line, "diff")) {
|
||||
startFile();
|
||||
|
||||
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
|
||||
const gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
|
||||
if ((values = gitDiffStart.exec(line))) {
|
||||
possibleOldName = _getFilename(null, values[1], config.dstPrefix);
|
||||
possibleNewName = _getFilename(null, values[2], config.srcPrefix);
|
||||
}
|
||||
|
||||
currentFile.isGitDiff = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!currentFile || // If we do not have a file yet, we should crete one
|
||||
(!currentFile.isGitDiff &&
|
||||
currentFile && // If we already have some file in progress and
|
||||
(utils.startsWith(line, oldFileNameHeader) && // If we get to an old file path header line
|
||||
// And is followed by the new file path header line and the hunk header line
|
||||
utils.startsWith(nxtLine, newFileNameHeader) &&
|
||||
utils.startsWith(afterNxtLine, hunkHeaderPrefix)))
|
||||
) {
|
||||
startFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to make sure that we have the three lines of the header.
|
||||
* This avoids cases like the ones described in:
|
||||
* - https://github.com/rtfpessoa/diff2html/issues/87
|
||||
*/
|
||||
if (
|
||||
(utils.startsWith(line, oldFileNameHeader) && utils.startsWith(nxtLine, newFileNameHeader)) ||
|
||||
(utils.startsWith(line, newFileNameHeader) && utils.startsWith(prevLine, oldFileNameHeader))
|
||||
) {
|
||||
/*
|
||||
* --- Date Timestamp[FractionalSeconds] TimeZone
|
||||
* --- 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.oldName &&
|
||||
utils.startsWith(line, "--- ") &&
|
||||
(values = getSrcFilename(line, config))
|
||||
) {
|
||||
currentFile.oldName = values;
|
||||
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* +++ Date Timestamp[FractionalSeconds] TimeZone
|
||||
* +++ 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.newName &&
|
||||
utils.startsWith(line, "+++ ") &&
|
||||
(values = getDstFilename(line, config))
|
||||
) {
|
||||
currentFile.newName = values;
|
||||
currentFile.language = getExtension(currentFile.newName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(currentFile && utils.startsWith(line, hunkHeaderPrefix)) ||
|
||||
(currentFile.isGitDiff && currentFile && currentFile.oldName && currentFile.newName && !currentBlock)
|
||||
) {
|
||||
startBlock(line);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* There are three types of diff lines. These lines are defined by the way they start.
|
||||
* 1. New line starts with: +
|
||||
* 2. Old line starts with: -
|
||||
* 3. Context line starts with: <SPACE>
|
||||
*/
|
||||
if (currentBlock && (utils.startsWith(line, "+") || utils.startsWith(line, "-") || utils.startsWith(line, " "))) {
|
||||
createLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
|
||||
|
||||
/*
|
||||
* Git diffs provide more information regarding files modes, renames, copies,
|
||||
* commits between changes and similarity indexes
|
||||
*/
|
||||
if ((values = oldMode.exec(line))) {
|
||||
currentFile.oldMode = values[1];
|
||||
} else if ((values = newMode.exec(line))) {
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = deletedFileMode.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
} else if ((values = newFileMode.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = copyFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = copyTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = renameFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = renameTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = binaryFiles.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
currentFile.oldName = _getFilename(null, values[1], config.srcPrefix);
|
||||
currentFile.newName = _getFilename(null, values[2], config.dstPrefix);
|
||||
startBlock("Binary file");
|
||||
} else if ((values = binaryDiff.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
startBlock(line);
|
||||
} else if ((values = similarityIndex.exec(line))) {
|
||||
currentFile.unchangedPercentage = values[1];
|
||||
} else if ((values = dissimilarityIndex.exec(line))) {
|
||||
currentFile.changedPercentage = values[1];
|
||||
} else if ((values = index.exec(line))) {
|
||||
currentFile.checksumBefore = values[1];
|
||||
currentFile.checksumAfter = values[2];
|
||||
values[3] && (currentFile.mode = values[3]);
|
||||
} else if ((values = combinedIndex.exec(line))) {
|
||||
currentFile.checksumBefore = [values[2], values[3]];
|
||||
currentFile.checksumAfter = values[1];
|
||||
} else if ((values = combinedMode.exec(line))) {
|
||||
currentFile.oldMode = [values[2], values[3]];
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = combinedNewFile.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = combinedDeletedFile.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
function getExtension(filename, language) {
|
||||
const nameSplit = filename.split(".");
|
||||
if (nameSplit.length > 1) {
|
||||
return nameSplit[nameSplit.length - 1];
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
function getSrcFilename(line, cfg) {
|
||||
return _getFilename("---", line, cfg.srcPrefix);
|
||||
}
|
||||
|
||||
function getDstFilename(line, cfg) {
|
||||
return _getFilename("\\+\\+\\+", line, cfg.dstPrefix);
|
||||
}
|
||||
|
||||
function _getFilename(linePrefix, line, extraPrefix) {
|
||||
const prefixes = ["a/", "b/", "i/", "w/", "c/", "o/"];
|
||||
if (extraPrefix) {
|
||||
prefixes.push(extraPrefix);
|
||||
}
|
||||
|
||||
let FilenameRegExp;
|
||||
if (linePrefix) {
|
||||
FilenameRegExp = new RegExp("^" + linePrefix + ' "?(.+?)"?$');
|
||||
} else {
|
||||
FilenameRegExp = new RegExp('^"?(.+?)"?$');
|
||||
}
|
||||
|
||||
let filename;
|
||||
const values = FilenameRegExp.exec(line);
|
||||
if (values && values[1]) {
|
||||
filename = values[1];
|
||||
const matchingPrefixes = prefixes.filter(function(p) {
|
||||
return filename.indexOf(p) === 0;
|
||||
});
|
||||
|
||||
if (matchingPrefixes[0]) {
|
||||
// Remove prefix if exists
|
||||
filename = filename.slice(matchingPrefixes[0].length);
|
||||
}
|
||||
|
||||
// Cleanup timestamps generated by the unified diff (diff command) as specified in
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
|
||||
// Ie: 2016-10-25 11:37:14.000000000 +0200
|
||||
filename = filename.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, "");
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
module.exports.DiffParser = new DiffParser();
|
||||
})();
|
||||
436
src/diff-parser.ts
Normal file
436
src/diff-parser.ts
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
import { DiffFile, DiffBlock, DiffLine, LineType } from "./render-utils";
|
||||
import { escapeForRegExp } from "./utils";
|
||||
|
||||
export interface DiffParserConfig {
|
||||
srcPrefix?: string;
|
||||
dstPrefix?: string;
|
||||
}
|
||||
|
||||
function getExtension(filename: string, language: string): string {
|
||||
const filenameParts = filename.split(".");
|
||||
return filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : language;
|
||||
}
|
||||
|
||||
function startsWithAny(str: string, prefixes: string[]): boolean {
|
||||
return prefixes.reduce<boolean>((startsWith, prefix) => startsWith || str.startsWith(prefix), false);
|
||||
}
|
||||
|
||||
const baseDiffFilenamePrefixes = ["a/", "b/", "i/", "w/", "c/", "o/"];
|
||||
function getFilename(line: string, linePrefix?: string, extraPrefix?: string): string {
|
||||
const prefixes = extraPrefix !== undefined ? [...baseDiffFilenamePrefixes, extraPrefix] : baseDiffFilenamePrefixes;
|
||||
|
||||
const FilenameRegExp = linePrefix
|
||||
? new RegExp(`^${escapeForRegExp(linePrefix)} "?(.+?)"?$`)
|
||||
: new RegExp('^"?(.+?)"?$');
|
||||
|
||||
const [, filename = ""] = FilenameRegExp.exec(line) || []; // TODO: Check if this is safe
|
||||
const matchingPrefix = prefixes.find(p => filename.indexOf(p) === 0);
|
||||
const fnameWithoutPrefix = matchingPrefix ? filename.slice(matchingPrefix.length) : filename;
|
||||
|
||||
// Cleanup timestamps generated by the unified diff (diff command) as specified in
|
||||
// https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
|
||||
// Ie: 2016-10-25 11:37:14.000000000 +0200
|
||||
return fnameWithoutPrefix.replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [-+]\d{4}.*$/, "");
|
||||
}
|
||||
|
||||
function getSrcFilename(line: string, srcPrefix?: string): string | undefined {
|
||||
return getFilename(line, "---", srcPrefix);
|
||||
}
|
||||
|
||||
function getDstFilename(line: string, dstPrefix?: string): string | undefined {
|
||||
return getFilename(line, "+++", dstPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Docs:
|
||||
* - Unified: https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html
|
||||
* - Git Diff: https://git-scm.com/docs/git-diff-tree#_raw_output_format
|
||||
* - Git Combined Diff: https://git-scm.com/docs/git-diff-tree#_combined_diff_format
|
||||
*
|
||||
*/
|
||||
export function parse(diffInput: string, config: DiffParserConfig = {}): DiffFile[] {
|
||||
const files: DiffFile[] = [];
|
||||
let currentFile: DiffFile | null = null;
|
||||
let currentBlock: DiffBlock | null = null;
|
||||
let oldLine: number | null = null;
|
||||
let oldLine2: number | null = null; // Used for combined diff
|
||||
let newLine: number | null = null;
|
||||
|
||||
let possibleOldName: string | null = null;
|
||||
let possibleNewName: string | null = null;
|
||||
|
||||
/* Diff Header */
|
||||
const oldFileNameHeader = "--- ";
|
||||
const newFileNameHeader = "+++ ";
|
||||
const hunkHeaderPrefix = "@@";
|
||||
|
||||
/* Diff */
|
||||
const oldMode = /^old mode (\d{6})/;
|
||||
const newMode = /^new mode (\d{6})/;
|
||||
const deletedFileMode = /^deleted file mode (\d{6})/;
|
||||
const newFileMode = /^new file mode (\d{6})/;
|
||||
|
||||
const copyFrom = /^copy from "?(.+)"?/;
|
||||
const copyTo = /^copy to "?(.+)"?/;
|
||||
|
||||
const renameFrom = /^rename from "?(.+)"?/;
|
||||
const renameTo = /^rename to "?(.+)"?/;
|
||||
|
||||
const similarityIndex = /^similarity index (\d+)%/;
|
||||
const dissimilarityIndex = /^dissimilarity index (\d+)%/;
|
||||
const index = /^index ([0-9a-z]+)\.\.([0-9a-z]+)\s*(\d{6})?/;
|
||||
|
||||
const binaryFiles = /^Binary files (.*) and (.*) differ/;
|
||||
const binaryDiff = /^GIT binary patch/;
|
||||
|
||||
/* Combined Diff */
|
||||
const combinedIndex = /^index ([0-9a-z]+),([0-9a-z]+)\.\.([0-9a-z]+)/;
|
||||
const combinedMode = /^mode (\d{6}),(\d{6})\.\.(\d{6})/;
|
||||
const combinedNewFile = /^new file mode (\d{6})/;
|
||||
const combinedDeletedFile = /^deleted file mode (\d{6}),(\d{6})/;
|
||||
|
||||
const diffLines = diffInput
|
||||
.replace(/\\ No newline at end of file/g, "")
|
||||
.replace(/\r\n?/g, "\n")
|
||||
.split("\n");
|
||||
|
||||
/* Add previous block(if exists) before start a new file */
|
||||
function saveBlock(): void {
|
||||
if (currentBlock !== null && currentFile !== null) {
|
||||
currentFile.blocks.push(currentBlock);
|
||||
currentBlock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add previous file(if exists) before start a new one
|
||||
* if it has name (to avoid binary files errors)
|
||||
*/
|
||||
function saveFile(): void {
|
||||
if (currentFile !== null) {
|
||||
if (!currentFile.oldName && possibleOldName !== null) {
|
||||
currentFile.oldName = possibleOldName;
|
||||
}
|
||||
|
||||
if (!currentFile.newName && possibleNewName !== null) {
|
||||
currentFile.newName = possibleNewName;
|
||||
}
|
||||
|
||||
if (currentFile.newName) {
|
||||
files.push(currentFile);
|
||||
currentFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
possibleOldName = null;
|
||||
possibleNewName = null;
|
||||
}
|
||||
|
||||
/* Create file structure */
|
||||
function startFile(): void {
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
// TODO: Avoid disabling types
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
currentFile = {
|
||||
blocks: [],
|
||||
deletedLines: 0,
|
||||
addedLines: 0
|
||||
};
|
||||
}
|
||||
|
||||
function startBlock(line: string): void {
|
||||
saveBlock();
|
||||
|
||||
let values;
|
||||
|
||||
/**
|
||||
* From Range:
|
||||
* -<start line>[,<number of lines>]
|
||||
*
|
||||
* To Range:
|
||||
* +<start line>[,<number of lines>]
|
||||
*
|
||||
* @@ from-file-range to-file-range @@
|
||||
*
|
||||
* @@@ from-file-range from-file-range to-file-range @@@
|
||||
*
|
||||
* number of lines is optional, if omited consider 0
|
||||
*/
|
||||
|
||||
if (currentFile !== null) {
|
||||
if ((values = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@.*/.exec(line))) {
|
||||
currentFile.isCombined = false;
|
||||
oldLine = parseInt(values[1], 10);
|
||||
newLine = parseInt(values[2], 10);
|
||||
} else if ((values = /^@@@ -(\d+)(?:,\d+)? -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@@.*/.exec(line))) {
|
||||
currentFile.isCombined = true;
|
||||
oldLine = parseInt(values[1], 10);
|
||||
oldLine2 = parseInt(values[2], 10);
|
||||
newLine = parseInt(values[3], 10);
|
||||
} else {
|
||||
if (line.startsWith(hunkHeaderPrefix)) {
|
||||
console.error("Failed to parse lines, starting in 0!");
|
||||
}
|
||||
|
||||
oldLine = 0;
|
||||
newLine = 0;
|
||||
currentFile.isCombined = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create block metadata */
|
||||
// TODO: Avoid disabling types
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
currentBlock = {
|
||||
lines: [],
|
||||
oldStartLine: oldLine,
|
||||
oldStartLine2: oldLine2,
|
||||
newStartLine: newLine,
|
||||
header: line
|
||||
};
|
||||
}
|
||||
|
||||
function createLine(line: string): void {
|
||||
if (currentFile === null || currentBlock === null || oldLine === null || newLine === null) return;
|
||||
|
||||
// TODO: Avoid disabling types
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const currentLine: DiffLine = {
|
||||
content: line
|
||||
};
|
||||
|
||||
const addedPrefixes = currentFile.isCombined ? ["+ ", " +", "++"] : ["+"];
|
||||
const deletedPrefixes = currentFile.isCombined ? ["- ", " -", "--"] : ["-"];
|
||||
|
||||
// TODO: Check if this makes sense for combined diff
|
||||
if (startsWithAny(line, addedPrefixes)) {
|
||||
currentFile.addedLines++;
|
||||
currentLine.type = LineType.INSERT;
|
||||
currentLine.oldNumber = undefined;
|
||||
currentLine.newNumber = newLine++;
|
||||
} else if (startsWithAny(line, deletedPrefixes)) {
|
||||
currentFile.deletedLines++;
|
||||
currentLine.type = LineType.DELETE;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = undefined;
|
||||
} else {
|
||||
currentLine.type = LineType.CONTEXT;
|
||||
currentLine.oldNumber = oldLine++;
|
||||
currentLine.newNumber = newLine++;
|
||||
}
|
||||
currentBlock.lines.push(currentLine);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if there is a hunk header coming before a new file starts
|
||||
*
|
||||
* Hunk header is a group of three lines started by ( `--- ` , `+++ ` , `@@` )
|
||||
*/
|
||||
function existHunkHeader(line: string, lineIdx: number): boolean {
|
||||
let idx = lineIdx;
|
||||
|
||||
while (idx < diffLines.length - 3) {
|
||||
if (line.startsWith("diff")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
diffLines[idx].startsWith(oldFileNameHeader) &&
|
||||
diffLines[idx + 1].startsWith(newFileNameHeader) &&
|
||||
diffLines[idx + 2].startsWith(hunkHeaderPrefix)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
diffLines.forEach(function(line, lineIndex) {
|
||||
// Unmerged paths, and possibly other non-diffable files
|
||||
// https://github.com/scottgonzalez/pretty-diff/issues/11
|
||||
// Also, remove some useless lines
|
||||
if (!line || line.startsWith("*")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used to store regex capture groups
|
||||
let values;
|
||||
|
||||
const prevLine = diffLines[lineIndex - 1];
|
||||
const nxtLine = diffLines[lineIndex + 1];
|
||||
const afterNxtLine = diffLines[lineIndex + 2];
|
||||
|
||||
if (line.startsWith("diff")) {
|
||||
startFile();
|
||||
|
||||
// diff --git a/blocked_delta_results.png b/blocked_delta_results.png
|
||||
const gitDiffStart = /^diff --git "?(.+)"? "?(.+)"?/;
|
||||
if ((values = gitDiffStart.exec(line))) {
|
||||
possibleOldName = getFilename(values[1], undefined, config.dstPrefix);
|
||||
possibleNewName = getFilename(values[2], undefined, config.srcPrefix);
|
||||
}
|
||||
|
||||
if (currentFile === null) {
|
||||
throw new Error("Where is my file !!!");
|
||||
}
|
||||
|
||||
currentFile.isGitDiff = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!currentFile || // If we do not have a file yet, we should crete one
|
||||
(!currentFile.isGitDiff &&
|
||||
currentFile && // If we already have some file in progress and
|
||||
(line.startsWith(oldFileNameHeader) && // If we get to an old file path header line
|
||||
// And is followed by the new file path header line and the hunk header line
|
||||
nxtLine.startsWith(newFileNameHeader) &&
|
||||
afterNxtLine.startsWith(hunkHeaderPrefix)))
|
||||
) {
|
||||
startFile();
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to make sure that we have the three lines of the header.
|
||||
* This avoids cases like the ones described in:
|
||||
* - https://github.com/rtfpessoa/diff2html/issues/87
|
||||
*/
|
||||
if (
|
||||
(line.startsWith(oldFileNameHeader) && nxtLine.startsWith(newFileNameHeader)) ||
|
||||
(line.startsWith(newFileNameHeader) && prevLine.startsWith(oldFileNameHeader))
|
||||
) {
|
||||
/*
|
||||
* --- Date Timestamp[FractionalSeconds] TimeZone
|
||||
* --- 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.oldName &&
|
||||
line.startsWith("--- ") &&
|
||||
(values = getSrcFilename(line, config.srcPrefix))
|
||||
) {
|
||||
currentFile.oldName = values;
|
||||
currentFile.language = getExtension(currentFile.oldName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* +++ Date Timestamp[FractionalSeconds] TimeZone
|
||||
* +++ 2002-02-21 23:30:39.942229878 -0800
|
||||
*/
|
||||
if (
|
||||
currentFile &&
|
||||
!currentFile.newName &&
|
||||
line.startsWith("+++ ") &&
|
||||
(values = getDstFilename(line, config.dstPrefix))
|
||||
) {
|
||||
currentFile.newName = values;
|
||||
currentFile.language = getExtension(currentFile.newName, currentFile.language);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(currentFile && line.startsWith(hunkHeaderPrefix)) ||
|
||||
(currentFile && currentFile.isGitDiff && currentFile.oldName && currentFile.newName && !currentBlock)
|
||||
) {
|
||||
startBlock(line);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* There are three types of diff lines. These lines are defined by the way they start.
|
||||
* 1. New line starts with: +
|
||||
* 2. Old line starts with: -
|
||||
* 3. Context line starts with: <SPACE>
|
||||
*/
|
||||
if (currentBlock && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
|
||||
createLine(line);
|
||||
return;
|
||||
}
|
||||
|
||||
const doesNotExistHunkHeader = !existHunkHeader(line, lineIndex);
|
||||
|
||||
if (currentFile === null) {
|
||||
throw new Error("Where is my file !!!");
|
||||
}
|
||||
|
||||
/*
|
||||
* Git diffs provide more information regarding files modes, renames, copies,
|
||||
* commits between changes and similarity indexes
|
||||
*/
|
||||
if ((values = oldMode.exec(line))) {
|
||||
currentFile.oldMode = values[1];
|
||||
} else if ((values = newMode.exec(line))) {
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = deletedFileMode.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
} else if ((values = newFileMode.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = copyFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = copyTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isCopy = true;
|
||||
} else if ((values = renameFrom.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.oldName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = renameTo.exec(line))) {
|
||||
if (doesNotExistHunkHeader) {
|
||||
currentFile.newName = values[1];
|
||||
}
|
||||
currentFile.isRename = true;
|
||||
} else if ((values = binaryFiles.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
currentFile.oldName = getFilename(values[1], undefined, config.srcPrefix);
|
||||
currentFile.newName = getFilename(values[2], undefined, config.dstPrefix);
|
||||
startBlock("Binary file");
|
||||
} else if ((values = binaryDiff.exec(line))) {
|
||||
currentFile.isBinary = true;
|
||||
startBlock(line);
|
||||
} else if ((values = similarityIndex.exec(line))) {
|
||||
currentFile.unchangedPercentage = parseInt(values[1], 10);
|
||||
} else if ((values = dissimilarityIndex.exec(line))) {
|
||||
currentFile.changedPercentage = parseInt(values[1], 10);
|
||||
} else if ((values = index.exec(line))) {
|
||||
currentFile.checksumBefore = values[1];
|
||||
currentFile.checksumAfter = values[2];
|
||||
values[3] && (currentFile.mode = values[3]);
|
||||
} else if ((values = combinedIndex.exec(line))) {
|
||||
currentFile.checksumBefore = [values[2], values[3]];
|
||||
currentFile.checksumAfter = values[1];
|
||||
} else if ((values = combinedMode.exec(line))) {
|
||||
currentFile.oldMode = [values[2], values[3]];
|
||||
currentFile.newMode = values[1];
|
||||
} else if ((values = combinedNewFile.exec(line))) {
|
||||
currentFile.newFileMode = values[1];
|
||||
currentFile.isNew = true;
|
||||
} else if ((values = combinedDeletedFile.exec(line))) {
|
||||
currentFile.deletedFileMode = values[1];
|
||||
currentFile.isDeleted = true;
|
||||
}
|
||||
});
|
||||
|
||||
saveBlock();
|
||||
saveFile();
|
||||
|
||||
return files;
|
||||
}
|
||||
70
src/diff2html.d.ts
vendored
70
src/diff2html.d.ts
vendored
|
|
@ -1,70 +0,0 @@
|
|||
// Type definitions for diff2html
|
||||
// Project: https://github.com/rtfpessoa/diff2html
|
||||
// Definitions by: rtfpessoa <https://github.com/rtfpessoa/>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
declare namespace Diff2Html {
|
||||
export interface Options {
|
||||
inputFormat?: "diff" | "json";
|
||||
outputFormat?: "line-by-line" | "side-by-side";
|
||||
showFiles?: boolean;
|
||||
diffStyle?: "word" | "char";
|
||||
matching?: "lines" | "words" | "none";
|
||||
matchWordsThreshold?: number;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
maxLineLengthHighlight?: number;
|
||||
templates?: object;
|
||||
rawTemplates?: object;
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
export interface Line {
|
||||
content: string;
|
||||
type: string;
|
||||
oldNumber: number;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
oldStartLine: number;
|
||||
oldStartLine2?: number;
|
||||
newStartLine: number;
|
||||
header: string;
|
||||
lines: Line[];
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
addedLines: number;
|
||||
deletedLines: number;
|
||||
isCombined: boolean;
|
||||
isGitDiff: boolean;
|
||||
oldName: string;
|
||||
newName: string;
|
||||
language: string;
|
||||
blocks: Block[];
|
||||
oldMode?: string;
|
||||
newMode?: string;
|
||||
deletedFileMode?: string;
|
||||
newFileMode?: string;
|
||||
isDeleted?: boolean;
|
||||
isNew?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRename?: boolean;
|
||||
unchangedPercentage?: number;
|
||||
changedPercentage?: number;
|
||||
checksumBefore?: string;
|
||||
checksumAfter?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export interface Diff2Html {
|
||||
getJsonFromDiff(input: string, configuration?: Options): Result[];
|
||||
getPrettyHtml(input: any, configuration?: Options): string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "diff2html" {
|
||||
var d2h: { Diff2Html: Diff2Html.Diff2Html };
|
||||
export = d2h;
|
||||
}
|
||||
113
src/diff2html.js
113
src/diff2html.js
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const diffParser = require("./diff-parser.js").DiffParser;
|
||||
const htmlPrinter = require("./html-printer.js").HtmlPrinter;
|
||||
const utils = require("./utils.js").Utils;
|
||||
|
||||
function Diff2Html() {}
|
||||
|
||||
const defaultConfig = {
|
||||
inputFormat: "diff",
|
||||
outputFormat: "line-by-line",
|
||||
showFiles: false,
|
||||
diffStyle: "word",
|
||||
matching: "none",
|
||||
matchWordsThreshold: 0.25,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200,
|
||||
maxLineLengthHighlight: 10000,
|
||||
templates: {},
|
||||
rawTemplates: {},
|
||||
renderNothingWhenEmpty: false
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates json object from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getJsonFromDiff = function(diffInput, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
return diffParser.generateDiffJson(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates the html diff. The config parameter configures the output/input formats and other options
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtml = function(diffInput, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
|
||||
let diffJson = diffInput;
|
||||
if (!cfg.inputFormat || cfg.inputFormat === "diff") {
|
||||
diffJson = diffParser.generateDiffJson(diffInput, cfg);
|
||||
}
|
||||
|
||||
let fileList = "";
|
||||
if (cfg.showFiles === true) {
|
||||
fileList = htmlPrinter.generateFileListSummary(diffJson, cfg);
|
||||
}
|
||||
|
||||
let diffOutput = "";
|
||||
if (cfg.outputFormat === "side-by-side") {
|
||||
diffOutput = htmlPrinter.generateSideBySideJsonHtml(diffJson, cfg);
|
||||
} else {
|
||||
diffOutput = htmlPrinter.generateLineByLineJsonHtml(diffJson, cfg);
|
||||
}
|
||||
|
||||
return fileList + diffOutput;
|
||||
};
|
||||
|
||||
/*
|
||||
* Deprecated methods - The following methods exist only to maintain compatibility with previous versions
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generates pretty html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromDiff = function(diffInput, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = "diff";
|
||||
cfg.outputFormat = "line-by-line";
|
||||
return this.getPrettyHtml(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettyHtmlFromJson = function(diffJson, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = "json";
|
||||
cfg.outputFormat = "line-by-line";
|
||||
return this.getPrettyHtml(diffJson, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from string diff input
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function(diffInput, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = "diff";
|
||||
cfg.outputFormat = "side-by-side";
|
||||
return this.getPrettyHtml(diffInput, cfg);
|
||||
};
|
||||
|
||||
/*
|
||||
* Generates pretty side by side html from a json object
|
||||
*/
|
||||
Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function(diffJson, config) {
|
||||
const cfg = utils.safeConfig(config, defaultConfig);
|
||||
cfg.inputFormat = "json";
|
||||
cfg.outputFormat = "side-by-side";
|
||||
return this.getPrettyHtml(diffJson, cfg);
|
||||
};
|
||||
|
||||
const diffObject = new Diff2Html();
|
||||
module.exports.Diff2Html = diffObject;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2Html = diffObject;
|
||||
})();
|
||||
50
src/diff2html.ts
Normal file
50
src/diff2html.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import * as DiffParser from "./diff-parser";
|
||||
import * as fileListPrinter from "./file-list-renderer";
|
||||
import LineByLineRenderer, { LineByLineRendererConfig, defaultLineByLineRendererConfig } from "./line-by-line-renderer";
|
||||
import SideBySideRenderer, { SideBySideRendererConfig, defaultSideBySideRendererConfig } from "./side-by-side-renderer";
|
||||
import { DiffFile } from "./render-utils";
|
||||
import HoganJsUtils, { HoganJsUtilsConfig } from "./hoganjs-utils";
|
||||
|
||||
type OutputFormatType = "line-by-line" | "side-by-side";
|
||||
|
||||
export interface Diff2HtmlConfig
|
||||
extends DiffParser.DiffParserConfig,
|
||||
LineByLineRendererConfig,
|
||||
SideBySideRendererConfig,
|
||||
HoganJsUtilsConfig {
|
||||
outputFormat?: OutputFormatType;
|
||||
showFiles?: boolean;
|
||||
}
|
||||
|
||||
export const defaultDiff2HtmlConfig = {
|
||||
...defaultLineByLineRendererConfig,
|
||||
...defaultSideBySideRendererConfig,
|
||||
outputFormat: "line-by-line" as OutputFormatType,
|
||||
showFiles: false
|
||||
};
|
||||
|
||||
export function parse(diffInput: string, configuration: Diff2HtmlConfig = {}): DiffFile[] {
|
||||
return DiffParser.parse(diffInput, { ...defaultDiff2HtmlConfig, ...configuration });
|
||||
}
|
||||
|
||||
export function html(diffInput: string | DiffFile[], configuration: Diff2HtmlConfig = {}): string {
|
||||
const config = { ...defaultDiff2HtmlConfig, ...configuration };
|
||||
|
||||
const diffJson = typeof diffInput === "string" ? DiffParser.parse(diffInput, config) : diffInput;
|
||||
|
||||
const hoganUtils = new HoganJsUtils(config);
|
||||
|
||||
const fileList = config.showFiles ? fileListPrinter.render(diffJson, hoganUtils) : "";
|
||||
|
||||
const diffOutput =
|
||||
config.outputFormat === "side-by-side"
|
||||
? new SideBySideRenderer(hoganUtils, config).render(diffJson)
|
||||
: new LineByLineRenderer(hoganUtils, config).render(diffJson);
|
||||
|
||||
// TODO: Review error handling
|
||||
if (diffOutput === undefined) {
|
||||
throw new Error("OMG we haz no diff. Why???");
|
||||
}
|
||||
|
||||
return fileList + diffOutput;
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* FileListPrinter (file-list-printer.js)
|
||||
* Author: nmatpt
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const printerUtils = require("./printer-utils.js").PrinterUtils;
|
||||
|
||||
let hoganUtils;
|
||||
|
||||
const baseTemplatesPath = "file-summary";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
|
||||
function FileListPrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
FileListPrinter.prototype.generateFileList = function(diffFiles) {
|
||||
const lineTemplate = hoganUtils.template(baseTemplatesPath, "line");
|
||||
|
||||
const files = diffFiles
|
||||
.map(function(file) {
|
||||
const fileTypeName = printerUtils.getFileTypeIcon(file);
|
||||
const iconTemplate = hoganUtils.template(iconsBaseTemplatesPath, fileTypeName);
|
||||
|
||||
return lineTemplate.render(
|
||||
{
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
oldName: file.oldName,
|
||||
newName: file.newName,
|
||||
fileName: printerUtils.getDiffName(file),
|
||||
deletedLines: "-" + file.deletedLines,
|
||||
addedLines: "+" + file.addedLines
|
||||
},
|
||||
{
|
||||
fileIcon: iconTemplate
|
||||
}
|
||||
);
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return hoganUtils.render(baseTemplatesPath, "wrapper", {
|
||||
filesNumber: diffFiles.length,
|
||||
files: files
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.FileListPrinter = FileListPrinter;
|
||||
})();
|
||||
32
src/file-list-renderer.ts
Normal file
32
src/file-list-renderer.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import * as renderUtils from "./render-utils";
|
||||
import HoganJsUtils from "./hoganjs-utils";
|
||||
|
||||
const baseTemplatesPath = "file-summary";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
|
||||
export function render(diffFiles: renderUtils.DiffFile[], hoganUtils: HoganJsUtils): string {
|
||||
const files = diffFiles
|
||||
.map(file =>
|
||||
hoganUtils.render(
|
||||
baseTemplatesPath,
|
||||
"line",
|
||||
{
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
oldName: file.oldName,
|
||||
newName: file.newName,
|
||||
fileName: renderUtils.filenameDiff(file),
|
||||
deletedLines: "-" + file.deletedLines,
|
||||
addedLines: "+" + file.addedLines
|
||||
},
|
||||
{
|
||||
fileIcon: hoganUtils.template(iconsBaseTemplatesPath, renderUtils.getFileIcon(file))
|
||||
}
|
||||
)
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
return hoganUtils.render(baseTemplatesPath, "wrapper", {
|
||||
filesNumber: diffFiles.length,
|
||||
files: files
|
||||
});
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Utils (hoganjs-utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const hogan = require("hogan.js");
|
||||
|
||||
const hoganTemplates = require("./diff2html-templates.js");
|
||||
|
||||
let extraTemplates;
|
||||
|
||||
function HoganJsUtils(configuration) {
|
||||
this.config = configuration || {};
|
||||
extraTemplates = this.config.templates || {};
|
||||
|
||||
const rawTemplates = this.config.rawTemplates || {};
|
||||
for (const templateName in rawTemplates) {
|
||||
if (rawTemplates.hasOwnProperty(templateName)) {
|
||||
if (!extraTemplates[templateName]) extraTemplates[templateName] = this.compile(rawTemplates[templateName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HoganJsUtils.prototype.render = function(namespace, view, params) {
|
||||
const template = this.template(namespace, view);
|
||||
if (template) {
|
||||
return template.render(params);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype.template = function(namespace, view) {
|
||||
const templateKey = this._templateKey(namespace, view);
|
||||
|
||||
return this._getTemplate(templateKey);
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._getTemplate = function(templateKey) {
|
||||
let template;
|
||||
|
||||
if (!this.config.noCache) {
|
||||
template = this._readFromCache(templateKey);
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
template = this._loadTemplate(templateKey);
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._loadTemplate = function(templateKey) {
|
||||
let template;
|
||||
|
||||
try {
|
||||
if (fs.readFileSync) {
|
||||
const templatesPath = path.resolve(__dirname, "templates");
|
||||
const templatePath = path.join(templatesPath, templateKey);
|
||||
const templateContent = fs.readFileSync(templatePath + ".mustache", "utf8");
|
||||
template = hogan.compile(templateContent);
|
||||
hoganTemplates[templateKey] = template;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to read (template: " + templateKey + ") from fs: " + e.message);
|
||||
}
|
||||
|
||||
return template;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._readFromCache = function(templateKey) {
|
||||
return extraTemplates[templateKey] || hoganTemplates[templateKey];
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype._templateKey = function(namespace, view) {
|
||||
return namespace + "-" + view;
|
||||
};
|
||||
|
||||
HoganJsUtils.prototype.compile = function(templateStr) {
|
||||
return hogan.compile(templateStr);
|
||||
};
|
||||
|
||||
module.exports.HoganJsUtils = HoganJsUtils;
|
||||
})();
|
||||
54
src/hoganjs-utils.ts
Normal file
54
src/hoganjs-utils.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import * as Hogan from "hogan.js";
|
||||
|
||||
import { defaultTemplates } from "./diff2html-templates";
|
||||
|
||||
export interface RawTemplates {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
export interface CompiledTemplates {
|
||||
[name: string]: Hogan.Template;
|
||||
}
|
||||
|
||||
export interface HoganJsUtilsConfig {
|
||||
compiledTemplates?: CompiledTemplates;
|
||||
rawTemplates?: RawTemplates;
|
||||
}
|
||||
|
||||
export default class HoganJsUtils {
|
||||
private preCompiledTemplates: CompiledTemplates;
|
||||
|
||||
constructor({ compiledTemplates = {}, rawTemplates = {} }: HoganJsUtilsConfig) {
|
||||
const compiledRawTemplates = Object.entries(rawTemplates).reduce<CompiledTemplates>(
|
||||
(previousTemplates, [name, templateString]) => {
|
||||
const compiledTemplate: Hogan.Template = Hogan.compile(templateString, { asString: false });
|
||||
return { ...previousTemplates, [name]: compiledTemplate };
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
this.preCompiledTemplates = { ...defaultTemplates, ...compiledTemplates, ...compiledRawTemplates };
|
||||
}
|
||||
|
||||
static compile(templateString: string): Hogan.Template {
|
||||
return Hogan.compile(templateString, { asString: false });
|
||||
}
|
||||
|
||||
render(namespace: string, view: string, params: Hogan.Context, partials?: Hogan.Partials, indent?: string): string {
|
||||
const templateKey = this.templateKey(namespace, view);
|
||||
try {
|
||||
const template = this.preCompiledTemplates[templateKey];
|
||||
return template.render(params, partials, indent);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not find template to render '${templateKey}'`);
|
||||
}
|
||||
}
|
||||
|
||||
template(namespace: string, view: string): Hogan.Template {
|
||||
return this.preCompiledTemplates[this.templateKey(namespace, view)];
|
||||
}
|
||||
|
||||
private templateKey(namespace: string, view: string): string {
|
||||
return `${namespace}-${view}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const LineByLinePrinter = require("./line-by-line-printer.js").LineByLinePrinter;
|
||||
const SideBySidePrinter = require("./side-by-side-printer.js").SideBySidePrinter;
|
||||
const FileListPrinter = require("./file-list-printer.js").FileListPrinter;
|
||||
|
||||
function HtmlPrinter() {}
|
||||
|
||||
HtmlPrinter.prototype.generateLineByLineJsonHtml = function(diffFiles, config) {
|
||||
const lineByLinePrinter = new LineByLinePrinter(config);
|
||||
return lineByLinePrinter.generateLineByLineJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
HtmlPrinter.prototype.generateSideBySideJsonHtml = function(diffFiles, config) {
|
||||
const sideBySidePrinter = new SideBySidePrinter(config);
|
||||
return sideBySidePrinter.generateSideBySideJsonHtml(diffFiles);
|
||||
};
|
||||
|
||||
HtmlPrinter.prototype.generateFileListSummary = function(diffJson, config) {
|
||||
const fileListPrinter = new FileListPrinter(config);
|
||||
return fileListPrinter.generateFileList(diffJson);
|
||||
};
|
||||
|
||||
module.exports.HtmlPrinter = new HtmlPrinter();
|
||||
})();
|
||||
|
|
@ -1,256 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* LineByLinePrinter (line-by-line-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const diffParser = require("./diff-parser.js").DiffParser;
|
||||
const printerUtils = require("./printer-utils.js").PrinterUtils;
|
||||
const utils = require("./utils.js").Utils;
|
||||
const Rematch = require("./rematch.js").Rematch;
|
||||
|
||||
let hoganUtils;
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "line-by-line";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
function LineByLinePrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
LineByLinePrinter.prototype.makeFileDiffHtml = function(file, diffs) {
|
||||
if (this.config.renderNothingWhenEmpty && file.blocks && !file.blocks.length) return "";
|
||||
|
||||
const fileDiffTemplate = hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: printerUtils.getDiffName(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.makeLineByLineHtmlWrapper = function(content) {
|
||||
return hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.generateLineByLineJsonHtml = function(diffFiles) {
|
||||
const that = this;
|
||||
const htmlDiffs = diffFiles.map(function(file) {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that._generateFileHtml(file);
|
||||
} else {
|
||||
diffs = that._generateEmptyDiff();
|
||||
}
|
||||
return that.makeFileDiffHtml(file, diffs);
|
||||
});
|
||||
|
||||
return this.makeLineByLineHtmlWrapper(htmlDiffs.join("\n"));
|
||||
};
|
||||
|
||||
const matcher = Rematch.rematch(function(a, b) {
|
||||
const amod = a.content.substr(1);
|
||||
const bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
LineByLinePrinter.prototype.makeColumnLineNumberHtml = function(block) {
|
||||
return hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||
diffParser: diffParser,
|
||||
blockHeader: utils.escape(block.header),
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line"
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateFileHtml = function(file) {
|
||||
const that = this;
|
||||
return file.blocks
|
||||
.map(function(block) {
|
||||
let lines = that.makeColumnLineNumberHtml(block);
|
||||
let oldLines = [];
|
||||
let newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
let matches;
|
||||
let insertType;
|
||||
let deleteType;
|
||||
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
|
||||
const maxLineSizeInBlock = Math.max.apply(
|
||||
null,
|
||||
[0].concat(
|
||||
oldLines.concat(newLines).map(function(elem) {
|
||||
return elem.content.length;
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const doMatching =
|
||||
comparisons < that.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
|
||||
(that.config.matching === "lines" || that.config.matching === "words");
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
let processedOldLines = [];
|
||||
let processedNewLines = [];
|
||||
|
||||
const common = Math.min(oldLines.length, newLines.length);
|
||||
|
||||
let oldLine, newLine;
|
||||
for (let j = 0; j < common; j++) {
|
||||
oldLine = oldLines[j];
|
||||
newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
const diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
processedOldLines += that.makeLineHtml(
|
||||
file.isCombined,
|
||||
deleteType,
|
||||
oldLine.oldNumber,
|
||||
oldLine.newNumber,
|
||||
diff.first.line,
|
||||
diff.first.prefix
|
||||
);
|
||||
processedNewLines += that.makeLineHtml(
|
||||
file.isCombined,
|
||||
insertType,
|
||||
newLine.oldNumber,
|
||||
newLine.newNumber,
|
||||
diff.second.line,
|
||||
diff.second.prefix
|
||||
);
|
||||
}
|
||||
|
||||
lines += processedOldLines + processedNewLines;
|
||||
lines += that._processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const line = block.lines[i];
|
||||
const escapedLine = utils.escape(line.content);
|
||||
|
||||
if (
|
||||
line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))
|
||||
) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
lines += that.makeLineHtml(file.isCombined, line.type, line.oldNumber, line.newNumber, escapedLine);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error("Unknown state in html line-by-line generator");
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
|
||||
return lines;
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._processLines = function(isCombined, oldLines, newLines) {
|
||||
let lines = "";
|
||||
|
||||
for (let i = 0; i < oldLines.length; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const oldEscapedLine = utils.escape(oldLine.content);
|
||||
lines += this.makeLineHtml(isCombined, oldLine.type, oldLine.oldNumber, oldLine.newNumber, oldEscapedLine);
|
||||
}
|
||||
|
||||
for (let j = 0; j < newLines.length; j++) {
|
||||
const newLine = newLines[j];
|
||||
const newEscapedLine = utils.escape(newLine.content);
|
||||
lines += this.makeLineHtml(isCombined, newLine.type, newLine.oldNumber, newLine.newNumber, newEscapedLine);
|
||||
}
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype.makeLineHtml = function(isCombined, type, oldNumber, newNumber, content, possiblePrefix) {
|
||||
const lineNumberTemplate = hoganUtils.render(baseTemplatesPath, "numbers", {
|
||||
oldNumber: utils.valueOrEmpty(oldNumber),
|
||||
newNumber: utils.valueOrEmpty(newNumber)
|
||||
});
|
||||
|
||||
let lineWithoutPrefix = content;
|
||||
let prefix = possiblePrefix;
|
||||
|
||||
if (!prefix) {
|
||||
const lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === " ") {
|
||||
prefix = " ";
|
||||
}
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: type,
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line",
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: lineNumberTemplate
|
||||
});
|
||||
};
|
||||
|
||||
LineByLinePrinter.prototype._generateEmptyDiff = function() {
|
||||
return hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
diffParser: diffParser
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.LineByLinePrinter = LineByLinePrinter;
|
||||
})();
|
||||
290
src/line-by-line-renderer.ts
Normal file
290
src/line-by-line-renderer.ts
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
import * as utils from "./utils";
|
||||
import HoganJsUtils from "./hoganjs-utils";
|
||||
import * as Rematch from "./rematch";
|
||||
import * as renderUtils from "./render-utils";
|
||||
|
||||
export interface LineByLineRendererConfig extends renderUtils.RenderConfig {
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
}
|
||||
|
||||
export const defaultLineByLineRendererConfig = {
|
||||
renderNothingWhenEmpty: false,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200,
|
||||
...renderUtils.defaultRenderConfig
|
||||
};
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "line-by-line";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
export default class LineByLineRenderer {
|
||||
private readonly hoganUtils: HoganJsUtils;
|
||||
private readonly config: typeof defaultLineByLineRendererConfig;
|
||||
|
||||
constructor(hoganUtils: HoganJsUtils, config: LineByLineRendererConfig) {
|
||||
this.hoganUtils = hoganUtils;
|
||||
this.config = { ...defaultLineByLineRendererConfig, ...config };
|
||||
}
|
||||
|
||||
render(diffFiles: renderUtils.DiffFile[]): string | undefined {
|
||||
const htmlDiffs = diffFiles.map(file => {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = this.generateFileHtml(file);
|
||||
} else {
|
||||
diffs = this.generateEmptyDiff();
|
||||
}
|
||||
return this.makeFileDiffHtml(file, diffs);
|
||||
});
|
||||
|
||||
return this.makeLineByLineHtmlWrapper(htmlDiffs.join("\n"));
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeFileDiffHtml(file: renderUtils.DiffFile, diffs: string): string {
|
||||
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return "";
|
||||
|
||||
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: renderUtils.filenameDiff(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeLineByLineHtmlWrapper(content: string): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeColumnLineNumberHtml(block: renderUtils.DiffBlock): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||
CSSLineClass: renderUtils.CSSLineClass,
|
||||
blockHeader: utils.escapeForHtml(block.header),
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line"
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeLineHtml(
|
||||
isCombined: boolean,
|
||||
type: renderUtils.CSSLineClass,
|
||||
content: string,
|
||||
oldNumber?: number,
|
||||
newNumber?: number,
|
||||
possiblePrefix?: string
|
||||
): string {
|
||||
const lineNumberTemplate = this.hoganUtils.render(baseTemplatesPath, "numbers", {
|
||||
oldNumber: oldNumber || "",
|
||||
newNumber: newNumber || ""
|
||||
});
|
||||
|
||||
let lineWithoutPrefix = content;
|
||||
let prefix = possiblePrefix;
|
||||
|
||||
if (!prefix) {
|
||||
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === " ") {
|
||||
prefix = " ";
|
||||
}
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: type,
|
||||
lineClass: "d2h-code-linenumber",
|
||||
contentClass: "d2h-code-line",
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: lineNumberTemplate
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
generateEmptyDiff(): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-line",
|
||||
CSSLineClass: renderUtils.CSSLineClass
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): string {
|
||||
let lines = "";
|
||||
|
||||
for (let i = 0; i < oldLines.length; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const oldEscapedLine = utils.escapeForHtml(oldLine.content);
|
||||
lines += this.makeLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(oldLine.type),
|
||||
oldEscapedLine,
|
||||
oldLine.oldNumber,
|
||||
oldLine.newNumber
|
||||
);
|
||||
}
|
||||
|
||||
for (let j = 0; j < newLines.length; j++) {
|
||||
const newLine = newLines[j];
|
||||
const newEscapedLine = utils.escapeForHtml(newLine.content);
|
||||
lines += this.makeLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(newLine.type),
|
||||
newEscapedLine,
|
||||
newLine.oldNumber,
|
||||
newLine.newNumber
|
||||
);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
generateFileHtml(file: renderUtils.DiffFile): string {
|
||||
const prefixSize = renderUtils.prefixLength(file.isCombined);
|
||||
const distance = Rematch.newDistanceFn((e: renderUtils.DiffLine) => e.content.substring(prefixSize));
|
||||
const matcher = Rematch.newMatcherFn(distance);
|
||||
|
||||
return file.blocks
|
||||
.map(block => {
|
||||
let lines = this.makeColumnLineNumberHtml(block);
|
||||
let oldLines: renderUtils.DiffLine[] = [];
|
||||
let newLines: renderUtils.DiffLine[] = [];
|
||||
|
||||
const processChangeBlock = (): void => {
|
||||
let matches;
|
||||
let insertType: renderUtils.CSSLineClass;
|
||||
let deleteType: renderUtils.CSSLineClass;
|
||||
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
|
||||
const maxLineSizeInBlock = Math.max.apply(
|
||||
null,
|
||||
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
||||
);
|
||||
|
||||
const doMatching =
|
||||
comparisons < this.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||
(this.config.matching === "lines" || this.config.matching === "words");
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
|
||||
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = renderUtils.CSSLineClass.INSERTS;
|
||||
deleteType = renderUtils.CSSLineClass.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(match => {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
let processedOldLines = "";
|
||||
let processedNewLines = "";
|
||||
|
||||
const common = Math.min(oldLines.length, newLines.length);
|
||||
|
||||
let oldLine, newLine;
|
||||
for (let j = 0; j < common; j++) {
|
||||
oldLine = oldLines[j];
|
||||
newLine = newLines[j];
|
||||
|
||||
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
|
||||
|
||||
processedOldLines += this.makeLineHtml(
|
||||
file.isCombined,
|
||||
deleteType,
|
||||
diff.oldLine.content,
|
||||
oldLine.oldNumber,
|
||||
oldLine.newNumber,
|
||||
diff.oldLine.prefix
|
||||
);
|
||||
processedNewLines += this.makeLineHtml(
|
||||
file.isCombined,
|
||||
insertType,
|
||||
diff.newLine.content,
|
||||
newLine.oldNumber,
|
||||
newLine.newNumber,
|
||||
diff.newLine.prefix
|
||||
);
|
||||
}
|
||||
|
||||
lines += processedOldLines + processedNewLines;
|
||||
lines += this.processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
};
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const diffLine = block.lines[i];
|
||||
const { prefix, line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
||||
const escapedLine = utils.escapeForHtml(line);
|
||||
|
||||
if (
|
||||
diffLine.type !== renderUtils.LineType.INSERT &&
|
||||
(newLines.length > 0 || (diffLine.type !== renderUtils.LineType.DELETE && oldLines.length > 0))
|
||||
) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (diffLine.type === renderUtils.LineType.CONTEXT) {
|
||||
lines += this.makeLineHtml(
|
||||
file.isCombined,
|
||||
renderUtils.toCSSClass(diffLine.type),
|
||||
escapedLine,
|
||||
diffLine.oldNumber,
|
||||
diffLine.newNumber,
|
||||
prefix
|
||||
);
|
||||
} else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
|
||||
lines += this.makeLineHtml(
|
||||
file.isCombined,
|
||||
renderUtils.toCSSClass(diffLine.type),
|
||||
escapedLine,
|
||||
diffLine.oldNumber,
|
||||
diffLine.newNumber,
|
||||
prefix
|
||||
);
|
||||
} else if (diffLine.type === renderUtils.LineType.DELETE) {
|
||||
oldLines.push(diffLine);
|
||||
} else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
|
||||
newLines.push(diffLine);
|
||||
} else {
|
||||
console.error("Unknown state in html line-by-line generator");
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
|
||||
return lines;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* PrinterUtils (printer-utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const jsDiff = require("diff");
|
||||
const utils = require("./utils.js").Utils;
|
||||
const Rematch = require("./rematch.js").Rematch;
|
||||
|
||||
const separator = "/";
|
||||
|
||||
function PrinterUtils() {}
|
||||
|
||||
PrinterUtils.prototype.separatePrefix = function(isCombined, line) {
|
||||
let prefix;
|
||||
let lineWithoutPrefix;
|
||||
|
||||
if (isCombined) {
|
||||
prefix = line.substring(0, 2);
|
||||
lineWithoutPrefix = line.substring(2);
|
||||
} else {
|
||||
prefix = line.substring(0, 1);
|
||||
lineWithoutPrefix = line.substring(1);
|
||||
}
|
||||
|
||||
return {
|
||||
prefix: prefix,
|
||||
line: lineWithoutPrefix
|
||||
};
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getHtmlId = function(file) {
|
||||
const hashCode = function(text) {
|
||||
let i, chr, len;
|
||||
let hash = 0;
|
||||
|
||||
for (i = 0, len = text.length; i < len; i++) {
|
||||
chr = text.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
return (
|
||||
"d2h-" +
|
||||
hashCode(this.getDiffName(file))
|
||||
.toString()
|
||||
.slice(-6)
|
||||
);
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getDiffName = function(file) {
|
||||
const oldFilename = unifyPath(file.oldName);
|
||||
const newFilename = unifyPath(file.newName);
|
||||
|
||||
if (
|
||||
oldFilename &&
|
||||
newFilename &&
|
||||
oldFilename !== newFilename &&
|
||||
!isDevNullName(oldFilename) &&
|
||||
!isDevNullName(newFilename)
|
||||
) {
|
||||
const prefixPaths = [];
|
||||
const suffixPaths = [];
|
||||
|
||||
const oldFilenameParts = oldFilename.split(separator);
|
||||
const newFilenameParts = newFilename.split(separator);
|
||||
|
||||
const oldFilenamePartsSize = oldFilenameParts.length;
|
||||
const newFilenamePartsSize = newFilenameParts.length;
|
||||
|
||||
let i = 0;
|
||||
let j = oldFilenamePartsSize - 1;
|
||||
let k = newFilenamePartsSize - 1;
|
||||
|
||||
while (i < j && i < k) {
|
||||
if (oldFilenameParts[i] === newFilenameParts[i]) {
|
||||
prefixPaths.push(newFilenameParts[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (j > i && k > i) {
|
||||
if (oldFilenameParts[j] === newFilenameParts[k]) {
|
||||
suffixPaths.unshift(newFilenameParts[k]);
|
||||
j -= 1;
|
||||
k -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const finalPrefix = prefixPaths.join(separator);
|
||||
const finalSuffix = suffixPaths.join(separator);
|
||||
|
||||
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
|
||||
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
|
||||
|
||||
if (finalPrefix.length && finalSuffix.length) {
|
||||
return (
|
||||
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix
|
||||
);
|
||||
} else if (finalPrefix.length) {
|
||||
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}";
|
||||
} else if (finalSuffix.length) {
|
||||
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix;
|
||||
}
|
||||
|
||||
return oldFilename + " → " + newFilename;
|
||||
} else if (newFilename && !isDevNullName(newFilename)) {
|
||||
return newFilename;
|
||||
} else if (oldFilename) {
|
||||
return oldFilename;
|
||||
}
|
||||
|
||||
return "unknown/file/path";
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.getFileTypeIcon = function(file) {
|
||||
let templateName = "file-changed";
|
||||
|
||||
if (file.isRename) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isCopy) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isNew) {
|
||||
templateName = "file-added";
|
||||
} else if (file.isDeleted) {
|
||||
templateName = "file-deleted";
|
||||
} else if (file.newName !== file.oldName) {
|
||||
// If file is not Added, not Deleted and the names changed it must be a rename :)
|
||||
templateName = "file-renamed";
|
||||
}
|
||||
|
||||
return templateName;
|
||||
};
|
||||
|
||||
PrinterUtils.prototype.diffHighlight = function(diffLine1, diffLine2, config) {
|
||||
let linePrefix1, linePrefix2, unprefixedLine1, unprefixedLine2;
|
||||
|
||||
let prefixSize = 1;
|
||||
|
||||
if (config.isCombined) {
|
||||
prefixSize = 2;
|
||||
}
|
||||
|
||||
linePrefix1 = diffLine1.substr(0, prefixSize);
|
||||
linePrefix2 = diffLine2.substr(0, prefixSize);
|
||||
unprefixedLine1 = diffLine1.substr(prefixSize);
|
||||
unprefixedLine2 = diffLine2.substr(prefixSize);
|
||||
|
||||
if (
|
||||
unprefixedLine1.length > config.maxLineLengthHighlight ||
|
||||
unprefixedLine2.length > config.maxLineLengthHighlight
|
||||
) {
|
||||
return {
|
||||
first: {
|
||||
prefix: linePrefix1,
|
||||
line: utils.escape(unprefixedLine1)
|
||||
},
|
||||
second: {
|
||||
prefix: linePrefix2,
|
||||
line: utils.escape(unprefixedLine2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let diff;
|
||||
if (config.diffStyle === "char") {
|
||||
diff = jsDiff.diffChars(unprefixedLine1, unprefixedLine2);
|
||||
} else {
|
||||
diff = jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
|
||||
}
|
||||
|
||||
let highlightedLine = "";
|
||||
|
||||
const changedWords = [];
|
||||
if (config.diffStyle === "word" && config.matching === "words") {
|
||||
let treshold = 0.25;
|
||||
|
||||
if (typeof config.matchWordsThreshold !== "undefined") {
|
||||
treshold = config.matchWordsThreshold;
|
||||
}
|
||||
|
||||
const matcher = Rematch.rematch(function(a, b) {
|
||||
const amod = a.value;
|
||||
const bmod = b.value;
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
const removed = diff.filter(function isRemoved(element) {
|
||||
return element.removed;
|
||||
});
|
||||
|
||||
const added = diff.filter(function isAdded(element) {
|
||||
return element.added;
|
||||
});
|
||||
|
||||
const chunks = matcher(added, removed);
|
||||
chunks.forEach(function(chunk) {
|
||||
if (chunk[0].length === 1 && chunk[1].length === 1) {
|
||||
const dist = Rematch.distance(chunk[0][0].value, chunk[1][0].value);
|
||||
if (dist < treshold) {
|
||||
changedWords.push(chunk[0][0]);
|
||||
changedWords.push(chunk[1][0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
diff.forEach(function(part) {
|
||||
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
|
||||
const elemType = part.added ? "ins" : part.removed ? "del" : null;
|
||||
const escapedValue = utils.escape(part.value);
|
||||
|
||||
if (elemType !== null) {
|
||||
highlightedLine += "<" + elemType + addClass + ">" + escapedValue + "</" + elemType + ">";
|
||||
} else {
|
||||
highlightedLine += escapedValue;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
first: {
|
||||
prefix: linePrefix1,
|
||||
line: removeIns(highlightedLine)
|
||||
},
|
||||
second: {
|
||||
prefix: linePrefix2,
|
||||
line: removeDel(highlightedLine)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function unifyPath(path) {
|
||||
if (path) {
|
||||
return path.replace("\\", "/");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function isDevNullName(name) {
|
||||
return name.indexOf("dev/null") !== -1;
|
||||
}
|
||||
|
||||
function removeIns(line) {
|
||||
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, "");
|
||||
}
|
||||
|
||||
function removeDel(line) {
|
||||
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, "");
|
||||
}
|
||||
|
||||
module.exports.PrinterUtils = new PrinterUtils();
|
||||
})();
|
||||
145
src/rematch.js
145
src/rematch.js
|
|
@ -1,145 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Rematch (rematch.js)
|
||||
* Matching two sequences of objects by similarity
|
||||
* Author: W. Illmeyer, Nexxar GmbH
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const Rematch = {};
|
||||
|
||||
/*
|
||||
Copyright (c) 2011 Andrei Mackenzie
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
function levenshtein(a, b) {
|
||||
if (a.length === 0) {
|
||||
return b.length;
|
||||
}
|
||||
if (b.length === 0) {
|
||||
return a.length;
|
||||
}
|
||||
|
||||
const matrix = [];
|
||||
|
||||
// Increment along the first column of each row
|
||||
let i;
|
||||
for (i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
|
||||
// Increment each column in the first row
|
||||
let j;
|
||||
for (j = 0; j <= a.length; j++) {
|
||||
matrix[0][j] = j;
|
||||
}
|
||||
|
||||
// Fill in the rest of the matrix
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j - 1] + 1, // Substitution
|
||||
Math.min(
|
||||
matrix[i][j - 1] + 1, // Insertion
|
||||
matrix[i - 1][j] + 1
|
||||
)
|
||||
); // Deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
|
||||
Rematch.levenshtein = levenshtein;
|
||||
|
||||
Rematch.distance = function distance(x, y) {
|
||||
x = x.trim();
|
||||
y = y.trim();
|
||||
const lev = levenshtein(x, y);
|
||||
const score = lev / (x.length + y.length);
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
Rematch.rematch = function rematch(distanceFunction) {
|
||||
function findBestMatch(a, b, cache) {
|
||||
let bestMatchDist = Infinity;
|
||||
let bestMatch;
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
for (let j = 0; j < b.length; ++j) {
|
||||
const cacheKey = JSON.stringify([a[i], b[j]]);
|
||||
var md;
|
||||
if (cache.hasOwnProperty(cacheKey)) {
|
||||
md = cache[cacheKey];
|
||||
} else {
|
||||
md = distanceFunction(a[i], b[j]);
|
||||
cache[cacheKey] = md;
|
||||
}
|
||||
if (md < bestMatchDist) {
|
||||
bestMatchDist = md;
|
||||
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
function group(a, b, level, cache) {
|
||||
if (typeof cache === "undefined") {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
const bm = findBestMatch(a, b, cache);
|
||||
|
||||
if (!level) {
|
||||
level = 0;
|
||||
}
|
||||
|
||||
if (!bm || a.length + b.length < 3) {
|
||||
return [[a, b]];
|
||||
}
|
||||
|
||||
const a1 = a.slice(0, bm.indexA);
|
||||
const b1 = b.slice(0, bm.indexB);
|
||||
const aMatch = [a[bm.indexA]];
|
||||
const bMatch = [b[bm.indexB]];
|
||||
const tailA = bm.indexA + 1;
|
||||
const tailB = bm.indexB + 1;
|
||||
const a2 = a.slice(tailA);
|
||||
const b2 = b.slice(tailB);
|
||||
|
||||
const group1 = group(a1, b1, level + 1, cache);
|
||||
const groupMatch = group(aMatch, bMatch, level + 1, cache);
|
||||
const group2 = group(a2, b2, level + 1, cache);
|
||||
let result = groupMatch;
|
||||
|
||||
if (bm.indexA > 0 || bm.indexB > 0) {
|
||||
result = group1.concat(result);
|
||||
}
|
||||
|
||||
if (a.length > tailA || b.length > tailB) {
|
||||
result = result.concat(group2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
module.exports.Rematch = Rematch;
|
||||
})();
|
||||
137
src/rematch.ts
Normal file
137
src/rematch.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Matching two sequences of objects by similarity
|
||||
* Author: W. Illmeyer, Nexxar GmbH
|
||||
*/
|
||||
|
||||
export type BestMatch = {
|
||||
indexA: number;
|
||||
indexB: number;
|
||||
score: number;
|
||||
};
|
||||
|
||||
/*
|
||||
Copyright (c) 2011 Andrei Mackenzie
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
||||
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export function levenshtein(a: string, b: string): number {
|
||||
if (a.length === 0) {
|
||||
return b.length;
|
||||
}
|
||||
if (b.length === 0) {
|
||||
return a.length;
|
||||
}
|
||||
|
||||
const matrix = [];
|
||||
|
||||
// Increment along the first column of each row
|
||||
let i;
|
||||
for (i = 0; i <= b.length; i++) {
|
||||
matrix[i] = [i];
|
||||
}
|
||||
|
||||
// Increment each column in the first row
|
||||
let j;
|
||||
for (j = 0; j <= a.length; j++) {
|
||||
matrix[0][j] = j;
|
||||
}
|
||||
|
||||
// Fill in the rest of the matrix
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||
matrix[i][j] = matrix[i - 1][j - 1];
|
||||
} else {
|
||||
matrix[i][j] = Math.min(
|
||||
matrix[i - 1][j - 1] + 1, // Substitution
|
||||
Math.min(
|
||||
matrix[i][j - 1] + 1, // Insertion
|
||||
matrix[i - 1][j] + 1
|
||||
)
|
||||
); // Deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[b.length][a.length];
|
||||
}
|
||||
|
||||
export type DistanceFn<T> = (x: T, y: T) => number;
|
||||
|
||||
export function newDistanceFn<T>(str: (value: T) => string): DistanceFn<T> {
|
||||
return (x: T, y: T): number => {
|
||||
const xValue = str(x).trim();
|
||||
const yValue = str(y).trim();
|
||||
const lev = levenshtein(xValue, yValue);
|
||||
const score = lev / (xValue.length + yValue.length);
|
||||
|
||||
return score;
|
||||
};
|
||||
}
|
||||
|
||||
export type MatcherFn<T> = (a: T[], b: T[], level?: number, cache?: Map<string, number>) => T[][][];
|
||||
|
||||
export function newMatcherFn<T>(distance: (x: T, y: T) => number): MatcherFn<T> {
|
||||
function findBestMatch(a: T[], b: T[], cache: Map<string, number> = new Map()): BestMatch | undefined {
|
||||
let bestMatchDist = Infinity;
|
||||
let bestMatch;
|
||||
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
for (let j = 0; j < b.length; ++j) {
|
||||
const cacheKey = JSON.stringify([a[i], b[j]]);
|
||||
let md;
|
||||
if (!(cache.has(cacheKey) && (md = cache.get(cacheKey)))) {
|
||||
md = distance(a[i], b[j]);
|
||||
cache.set(cacheKey, md);
|
||||
}
|
||||
if (md < bestMatchDist) {
|
||||
bestMatchDist = md;
|
||||
bestMatch = { indexA: i, indexB: j, score: bestMatchDist };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
function group(a: T[], b: T[], level = 0, cache: Map<string, number> = new Map()): T[][][] {
|
||||
const bm = findBestMatch(a, b, cache);
|
||||
|
||||
if (!bm || a.length + b.length < 3) {
|
||||
return [[a, b]];
|
||||
}
|
||||
|
||||
const a1 = a.slice(0, bm.indexA);
|
||||
const b1 = b.slice(0, bm.indexB);
|
||||
const aMatch = [a[bm.indexA]];
|
||||
const bMatch = [b[bm.indexB]];
|
||||
const tailA = bm.indexA + 1;
|
||||
const tailB = bm.indexB + 1;
|
||||
const a2 = a.slice(tailA);
|
||||
const b2 = b.slice(tailB);
|
||||
|
||||
const group1 = group(a1, b1, level + 1, cache);
|
||||
const groupMatch = group(aMatch, bMatch, level + 1, cache);
|
||||
const group2 = group(a2, b2, level + 1, cache);
|
||||
let result = groupMatch;
|
||||
|
||||
if (bm.indexA > 0 || bm.indexB > 0) {
|
||||
result = group1.concat(result);
|
||||
}
|
||||
|
||||
if (a.length > tailA || b.length > tailB) {
|
||||
result = result.concat(group2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
343
src/render-utils.ts
Normal file
343
src/render-utils.ts
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
import * as jsDiff from "diff";
|
||||
|
||||
import { unifyPath, escapeForHtml, hashCode } from "./utils";
|
||||
import * as rematch from "./rematch";
|
||||
|
||||
export type DiffLineParts = {
|
||||
prefix: string;
|
||||
line: string;
|
||||
};
|
||||
|
||||
export enum CSSLineClass {
|
||||
INSERTS = "d2h-ins",
|
||||
DELETES = "d2h-del",
|
||||
CONTEXT = "d2h-cntx",
|
||||
INFO = "d2h-info",
|
||||
INSERT_CHANGES = "d2h-ins d2h-change",
|
||||
DELETE_CHANGES = "d2h-del d2h-change"
|
||||
}
|
||||
|
||||
export enum LineType {
|
||||
INSERT = "insert",
|
||||
DELETE = "delete",
|
||||
CONTEXT = "context"
|
||||
}
|
||||
|
||||
interface DiffLineDeleted {
|
||||
type: LineType.DELETE;
|
||||
oldNumber: number;
|
||||
newNumber: undefined;
|
||||
}
|
||||
|
||||
interface DiffLineInserted {
|
||||
type: LineType.INSERT;
|
||||
oldNumber: undefined;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
interface DiffLineContext {
|
||||
type: LineType.CONTEXT;
|
||||
oldNumber: number;
|
||||
newNumber: number;
|
||||
}
|
||||
|
||||
export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export interface DiffBlock {
|
||||
oldStartLine: number;
|
||||
oldStartLine2?: number;
|
||||
newStartLine: number;
|
||||
header: string;
|
||||
lines: DiffLine[];
|
||||
}
|
||||
|
||||
interface DiffFileName {
|
||||
oldName: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
export interface DiffFile extends DiffFileName {
|
||||
addedLines: number;
|
||||
deletedLines: number;
|
||||
isCombined: boolean;
|
||||
isGitDiff: boolean;
|
||||
language: string;
|
||||
blocks: DiffBlock[];
|
||||
oldMode?: string | string[];
|
||||
newMode?: string;
|
||||
deletedFileMode?: string;
|
||||
newFileMode?: string;
|
||||
isDeleted?: boolean;
|
||||
isNew?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRename?: boolean;
|
||||
isBinary?: boolean;
|
||||
unchangedPercentage?: number;
|
||||
changedPercentage?: number;
|
||||
checksumBefore?: string | string[];
|
||||
checksumAfter?: string;
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export type LineMatchingType = "lines" | "words" | "none";
|
||||
export type DiffStyleType = "word" | "char";
|
||||
|
||||
export interface RenderConfig {
|
||||
matching?: LineMatchingType;
|
||||
matchWordsThreshold?: number;
|
||||
maxLineLengthHighlight?: number;
|
||||
diffStyle?: DiffStyleType;
|
||||
}
|
||||
|
||||
export const defaultRenderConfig = {
|
||||
matching: "none" as LineMatchingType,
|
||||
matchWordsThreshold: 0.25,
|
||||
maxLineLengthHighlight: 10000,
|
||||
diffStyle: "word" as DiffStyleType
|
||||
};
|
||||
|
||||
type HighlightedLines = {
|
||||
oldLine: {
|
||||
prefix: string;
|
||||
content: string;
|
||||
};
|
||||
newLine: {
|
||||
prefix: string;
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
|
||||
const separator = "/";
|
||||
const distance = rematch.newDistanceFn((change: jsDiff.Change) => change.value);
|
||||
const matcher = rematch.newMatcherFn(distance);
|
||||
|
||||
function isDevNullName(name: string): boolean {
|
||||
return name.indexOf("dev/null") !== -1;
|
||||
}
|
||||
|
||||
function removeInsElements(line: string): string {
|
||||
return line.replace(/(<ins[^>]*>((.|\n)*?)<\/ins>)/g, "");
|
||||
}
|
||||
|
||||
function removeDelElements(line: string): string {
|
||||
return line.replace(/(<del[^>]*>((.|\n)*?)<\/del>)/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from LineType to CSSLineClass
|
||||
*/
|
||||
export function toCSSClass(lineType: LineType): CSSLineClass {
|
||||
switch (lineType) {
|
||||
case LineType.CONTEXT:
|
||||
return CSSLineClass.CONTEXT;
|
||||
case LineType.INSERT:
|
||||
return CSSLineClass.INSERTS;
|
||||
case LineType.DELETE:
|
||||
return CSSLineClass.DELETES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix length of the hunk lines in the diff
|
||||
*/
|
||||
export function prefixLength(isCombined: boolean): number {
|
||||
return isCombined ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deconstructs diff @line by separating the content from the prefix type
|
||||
*/
|
||||
export function deconstructLine(line: string, isCombined: boolean): DiffLineParts {
|
||||
const indexToSplit = prefixLength(isCombined);
|
||||
return {
|
||||
prefix: line.substring(0, indexToSplit),
|
||||
line: line.substring(indexToSplit)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates pretty filename diffs
|
||||
*
|
||||
* e.g.:
|
||||
* 1. file = { oldName: "my/path/to/file.js", newName: "my/path/to/new-file.js" }
|
||||
* returns "my/path/to/{file.js → new-file.js}"
|
||||
* 2. file = { oldName: "my/path/to/file.js", newName: "very/new/path/to/new-file.js" }
|
||||
* returns "my/path/to/file.js → very/new/path/to/new-file.js"
|
||||
* 3. file = { oldName: "my/path/to/file.js", newName: "my/path/for/file.js" }
|
||||
* returns "my/path/{to → for}/file.js"
|
||||
*/
|
||||
export function filenameDiff(file: DiffFileName): string {
|
||||
// TODO: Review this huuuuuge piece of code, do we need this?
|
||||
// TODO: Move unify path to parsing
|
||||
const oldFilename = unifyPath(file.oldName);
|
||||
const newFilename = unifyPath(file.newName);
|
||||
|
||||
if (oldFilename !== newFilename && !isDevNullName(oldFilename) && !isDevNullName(newFilename)) {
|
||||
const prefixPaths = [];
|
||||
const suffixPaths = [];
|
||||
|
||||
const oldFilenameParts = oldFilename.split(separator);
|
||||
const newFilenameParts = newFilename.split(separator);
|
||||
|
||||
const oldFilenamePartsSize = oldFilenameParts.length;
|
||||
const newFilenamePartsSize = newFilenameParts.length;
|
||||
|
||||
let i = 0;
|
||||
let j = oldFilenamePartsSize - 1;
|
||||
let k = newFilenamePartsSize - 1;
|
||||
|
||||
while (i < j && i < k) {
|
||||
if (oldFilenameParts[i] === newFilenameParts[i]) {
|
||||
prefixPaths.push(newFilenameParts[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (j > i && k > i) {
|
||||
if (oldFilenameParts[j] === newFilenameParts[k]) {
|
||||
suffixPaths.unshift(newFilenameParts[k]);
|
||||
j -= 1;
|
||||
k -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const finalPrefix = prefixPaths.join(separator);
|
||||
const finalSuffix = suffixPaths.join(separator);
|
||||
|
||||
const oldRemainingPath = oldFilenameParts.slice(i, j + 1).join(separator);
|
||||
const newRemainingPath = newFilenameParts.slice(i, k + 1).join(separator);
|
||||
|
||||
if (finalPrefix.length && finalSuffix.length) {
|
||||
return (
|
||||
finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix
|
||||
);
|
||||
} else if (finalPrefix.length) {
|
||||
return finalPrefix + separator + "{" + oldRemainingPath + " → " + newRemainingPath + "}";
|
||||
} else if (finalSuffix.length) {
|
||||
return "{" + oldRemainingPath + " → " + newRemainingPath + "}" + separator + finalSuffix;
|
||||
}
|
||||
|
||||
return oldFilename + " → " + newFilename;
|
||||
} else if (!isDevNullName(newFilename)) {
|
||||
return newFilename;
|
||||
} else {
|
||||
return oldFilename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique string numerical identifier based on the names of the file diff
|
||||
*/
|
||||
export function getHtmlId(file: DiffFileName): string {
|
||||
return `d2h-${hashCode(filenameDiff(file))
|
||||
.toString()
|
||||
.slice(-6)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the correct icon name for the file
|
||||
*/
|
||||
export function getFileIcon(file: DiffFile): string {
|
||||
let templateName = "file-changed";
|
||||
|
||||
if (file.isRename) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isCopy) {
|
||||
templateName = "file-renamed";
|
||||
} else if (file.isNew) {
|
||||
templateName = "file-added";
|
||||
} else if (file.isDeleted) {
|
||||
templateName = "file-deleted";
|
||||
} else if (file.newName !== file.oldName) {
|
||||
// If file is not Added, not Deleted and the names changed it must be a rename :)
|
||||
templateName = "file-renamed";
|
||||
}
|
||||
|
||||
return templateName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique string numerical identifier based on the names of the file diff
|
||||
*/
|
||||
export function diffHighlight(
|
||||
diffLine1: string,
|
||||
diffLine2: string,
|
||||
isCombined: boolean,
|
||||
config: RenderConfig
|
||||
): HighlightedLines {
|
||||
const { matching, maxLineLengthHighlight, matchWordsThreshold, diffStyle } = { ...defaultRenderConfig, ...config };
|
||||
const prefixLengthVal = prefixLength(isCombined);
|
||||
|
||||
const linePrefix1 = diffLine1.substr(0, prefixLengthVal);
|
||||
const unprefixedLine1 = diffLine1.substr(prefixLengthVal);
|
||||
|
||||
const linePrefix2 = diffLine2.substr(0, prefixLengthVal);
|
||||
const unprefixedLine2 = diffLine2.substr(prefixLengthVal);
|
||||
|
||||
if (unprefixedLine1.length > maxLineLengthHighlight || unprefixedLine2.length > maxLineLengthHighlight) {
|
||||
return {
|
||||
oldLine: {
|
||||
prefix: linePrefix1,
|
||||
content: escapeForHtml(unprefixedLine1)
|
||||
},
|
||||
newLine: {
|
||||
prefix: linePrefix2,
|
||||
content: escapeForHtml(unprefixedLine2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const diff =
|
||||
diffStyle === "char"
|
||||
? jsDiff.diffChars(unprefixedLine1, unprefixedLine2)
|
||||
: jsDiff.diffWordsWithSpace(unprefixedLine1, unprefixedLine2);
|
||||
|
||||
const changedWords: jsDiff.Change[] = [];
|
||||
if (diffStyle === "word" && matching === "words") {
|
||||
let treshold = 0.25;
|
||||
|
||||
if (typeof matchWordsThreshold !== "undefined") {
|
||||
treshold = matchWordsThreshold;
|
||||
}
|
||||
|
||||
const removed = diff.filter(element => element.removed);
|
||||
const added = diff.filter(element => element.added);
|
||||
const chunks = matcher(added, removed);
|
||||
chunks.forEach(chunk => {
|
||||
if (chunk[0].length === 1 && chunk[1].length === 1) {
|
||||
const dist = distance(chunk[0][0], chunk[1][0]);
|
||||
if (dist < treshold) {
|
||||
changedWords.push(chunk[0][0]);
|
||||
changedWords.push(chunk[1][0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const highlightedLine = diff.reduce((highlightedLine, part) => {
|
||||
const elemType = part.added ? "ins" : part.removed ? "del" : null;
|
||||
const addClass = changedWords.indexOf(part) > -1 ? ' class="d2h-change"' : "";
|
||||
const escapedValue = escapeForHtml(part.value);
|
||||
|
||||
return elemType !== null
|
||||
? `${highlightedLine}<${elemType}${addClass}>${escapedValue}</${elemType}>`
|
||||
: `${highlightedLine}${escapedValue}`;
|
||||
}, "");
|
||||
|
||||
return {
|
||||
oldLine: {
|
||||
prefix: linePrefix1,
|
||||
content: removeInsElements(highlightedLine)
|
||||
},
|
||||
newLine: {
|
||||
prefix: linePrefix2,
|
||||
content: removeDelElements(highlightedLine)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* HtmlPrinter (html-printer.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const diffParser = require("./diff-parser.js").DiffParser;
|
||||
const printerUtils = require("./printer-utils.js").PrinterUtils;
|
||||
const utils = require("./utils.js").Utils;
|
||||
const Rematch = require("./rematch.js").Rematch;
|
||||
|
||||
let hoganUtils;
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "side-by-side";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
const matcher = Rematch.rematch(function(a, b) {
|
||||
const amod = a.content.substr(1);
|
||||
const bmod = b.content.substr(1);
|
||||
|
||||
return Rematch.distance(amod, bmod);
|
||||
});
|
||||
|
||||
function SideBySidePrinter(config) {
|
||||
this.config = config;
|
||||
|
||||
const HoganJsUtils = require("./hoganjs-utils.js").HoganJsUtils;
|
||||
hoganUtils = new HoganJsUtils(config);
|
||||
}
|
||||
|
||||
SideBySidePrinter.prototype.makeDiffHtml = function(file, diffs) {
|
||||
const fileDiffTemplate = hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = hoganUtils.template(tagsBaseTemplatesPath, printerUtils.getFileTypeIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: printerUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: printerUtils.getDiffName(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideJsonHtml = function(diffFiles) {
|
||||
const that = this;
|
||||
|
||||
const content = diffFiles
|
||||
.map(function(file) {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = that.generateSideBySideFileHtml(file);
|
||||
} else {
|
||||
diffs = that.generateEmptyDiff();
|
||||
}
|
||||
|
||||
return that.makeDiffHtml(file, diffs);
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.makeSideHtml = function(blockHeader) {
|
||||
return hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||
diffParser: diffParser,
|
||||
blockHeader: utils.escape(blockHeader),
|
||||
lineClass: "d2h-code-side-linenumber",
|
||||
contentClass: "d2h-code-side-line"
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSideBySideFileHtml = function(file) {
|
||||
const that = this;
|
||||
const fileHtml = {};
|
||||
fileHtml.left = "";
|
||||
fileHtml.right = "";
|
||||
|
||||
file.blocks.forEach(function(block) {
|
||||
fileHtml.left += that.makeSideHtml(block.header);
|
||||
fileHtml.right += that.makeSideHtml("");
|
||||
|
||||
let oldLines = [];
|
||||
let newLines = [];
|
||||
|
||||
function processChangeBlock() {
|
||||
let matches;
|
||||
let insertType;
|
||||
let deleteType;
|
||||
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
|
||||
const maxLineSizeInBlock = Math.max.apply(
|
||||
null,
|
||||
oldLines.concat(newLines).map(function(elem) {
|
||||
return elem.length;
|
||||
})
|
||||
);
|
||||
|
||||
const doMatching =
|
||||
comparisons < that.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < that.config.maxLineSizeInBlockForComparison &&
|
||||
(that.config.matching === "lines" || that.config.matching === "words");
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = diffParser.LINE_TYPE.INSERT_CHANGES;
|
||||
deleteType = diffParser.LINE_TYPE.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = diffParser.LINE_TYPE.INSERTS;
|
||||
deleteType = diffParser.LINE_TYPE.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(function(match) {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
const common = Math.min(oldLines.length, newLines.length);
|
||||
const max = Math.max(oldLines.length, newLines.length);
|
||||
|
||||
for (let j = 0; j < common; j++) {
|
||||
const oldLine = oldLines[j];
|
||||
const newLine = newLines[j];
|
||||
|
||||
that.config.isCombined = file.isCombined;
|
||||
|
||||
const diff = printerUtils.diffHighlight(oldLine.content, newLine.content, that.config);
|
||||
|
||||
fileHtml.left += that.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
deleteType,
|
||||
oldLine.oldNumber,
|
||||
diff.first.line,
|
||||
diff.first.prefix
|
||||
);
|
||||
fileHtml.right += that.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
insertType,
|
||||
newLine.newNumber,
|
||||
diff.second.line,
|
||||
diff.second.prefix
|
||||
);
|
||||
}
|
||||
|
||||
if (max > common) {
|
||||
const oldSlice = oldLines.slice(common);
|
||||
const newSlice = newLines.slice(common);
|
||||
|
||||
const tmpHtml = that.processLines(file.isCombined, oldSlice, newSlice);
|
||||
fileHtml.left += tmpHtml.left;
|
||||
fileHtml.right += tmpHtml.right;
|
||||
}
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const line = block.lines[i];
|
||||
const prefix = line.content[0];
|
||||
const escapedLine = utils.escape(line.content.substr(1));
|
||||
|
||||
if (
|
||||
line.type !== diffParser.LINE_TYPE.INSERTS &&
|
||||
(newLines.length > 0 || (line.type !== diffParser.LINE_TYPE.DELETES && oldLines.length > 0))
|
||||
) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (line.type === diffParser.LINE_TYPE.CONTEXT) {
|
||||
fileHtml.left += that.generateSingleLineHtml(file.isCombined, line.type, line.oldNumber, escapedLine, prefix);
|
||||
fileHtml.right += that.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
line.type,
|
||||
line.newNumber,
|
||||
escapedLine,
|
||||
prefix
|
||||
);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && !oldLines.length) {
|
||||
fileHtml.left += that.generateSingleLineHtml(file.isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
|
||||
fileHtml.right += that.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
line.type,
|
||||
line.newNumber,
|
||||
escapedLine,
|
||||
prefix
|
||||
);
|
||||
} else if (line.type === diffParser.LINE_TYPE.DELETES) {
|
||||
oldLines.push(line);
|
||||
} else if (line.type === diffParser.LINE_TYPE.INSERTS && Boolean(oldLines.length)) {
|
||||
newLines.push(line);
|
||||
} else {
|
||||
console.error("unknown state in html side-by-side generator");
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.processLines = function(isCombined, oldLines, newLines) {
|
||||
const that = this;
|
||||
const fileHtml = {};
|
||||
fileHtml.left = "";
|
||||
fileHtml.right = "";
|
||||
|
||||
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (let i = 0; i < maxLinesNumber; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const newLine = newLines[i];
|
||||
var oldContent;
|
||||
var newContent;
|
||||
var oldPrefix;
|
||||
var newPrefix;
|
||||
|
||||
if (oldLine) {
|
||||
oldContent = utils.escape(oldLine.content.substr(1));
|
||||
oldPrefix = oldLine.content[0];
|
||||
}
|
||||
|
||||
if (newLine) {
|
||||
newContent = utils.escape(newLine.content.substr(1));
|
||||
newPrefix = newLine.content[0];
|
||||
}
|
||||
|
||||
if (oldLine && newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(
|
||||
isCombined,
|
||||
oldLine.type,
|
||||
oldLine.oldNumber,
|
||||
oldContent,
|
||||
oldPrefix
|
||||
);
|
||||
fileHtml.right += that.generateSingleLineHtml(
|
||||
isCombined,
|
||||
newLine.type,
|
||||
newLine.newNumber,
|
||||
newContent,
|
||||
newPrefix
|
||||
);
|
||||
} else if (oldLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(
|
||||
isCombined,
|
||||
oldLine.type,
|
||||
oldLine.oldNumber,
|
||||
oldContent,
|
||||
oldPrefix
|
||||
);
|
||||
fileHtml.right += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
|
||||
} else if (newLine) {
|
||||
fileHtml.left += that.generateSingleLineHtml(isCombined, diffParser.LINE_TYPE.CONTEXT, "", "", "");
|
||||
fileHtml.right += that.generateSingleLineHtml(
|
||||
isCombined,
|
||||
newLine.type,
|
||||
newLine.newNumber,
|
||||
newContent,
|
||||
newPrefix
|
||||
);
|
||||
} else {
|
||||
console.error("How did it get here?");
|
||||
}
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateSingleLineHtml = function(isCombined, type, number, content, possiblePrefix) {
|
||||
let lineWithoutPrefix = content;
|
||||
let prefix = possiblePrefix;
|
||||
let lineClass = "d2h-code-side-linenumber";
|
||||
let contentClass = "d2h-code-side-line";
|
||||
|
||||
if (!number && !content) {
|
||||
lineClass += " d2h-code-side-emptyplaceholder";
|
||||
contentClass += " d2h-code-side-emptyplaceholder";
|
||||
type += " d2h-emptyplaceholder";
|
||||
prefix = " ";
|
||||
lineWithoutPrefix = " ";
|
||||
} else if (!prefix) {
|
||||
const lineWithPrefix = printerUtils.separatePrefix(isCombined, content);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === " ") {
|
||||
prefix = " ";
|
||||
}
|
||||
|
||||
return hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: type,
|
||||
lineClass: lineClass,
|
||||
contentClass: contentClass,
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: number
|
||||
});
|
||||
};
|
||||
|
||||
SideBySidePrinter.prototype.generateEmptyDiff = function() {
|
||||
const fileHtml = {};
|
||||
fileHtml.right = "";
|
||||
|
||||
fileHtml.left = hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-side-line",
|
||||
diffParser: diffParser
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
};
|
||||
|
||||
module.exports.SideBySidePrinter = SideBySidePrinter;
|
||||
})();
|
||||
352
src/side-by-side-renderer.ts
Normal file
352
src/side-by-side-renderer.ts
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
import * as utils from "./utils";
|
||||
import HoganJsUtils from "./hoganjs-utils";
|
||||
import * as Rematch from "./rematch";
|
||||
import * as renderUtils from "./render-utils";
|
||||
|
||||
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
|
||||
renderNothingWhenEmpty?: boolean;
|
||||
matchingMaxComparisons?: number;
|
||||
maxLineSizeInBlockForComparison?: number;
|
||||
}
|
||||
|
||||
export const defaultSideBySideRendererConfig = {
|
||||
...renderUtils.defaultRenderConfig,
|
||||
renderNothingWhenEmpty: false,
|
||||
matchingMaxComparisons: 2500,
|
||||
maxLineSizeInBlockForComparison: 200
|
||||
};
|
||||
|
||||
type FileHtml = {
|
||||
right: string;
|
||||
left: string;
|
||||
};
|
||||
|
||||
const genericTemplatesPath = "generic";
|
||||
const baseTemplatesPath = "side-by-side";
|
||||
const iconsBaseTemplatesPath = "icon";
|
||||
const tagsBaseTemplatesPath = "tag";
|
||||
|
||||
export default class SideBySideRenderer {
|
||||
private readonly hoganUtils: HoganJsUtils;
|
||||
private readonly config: typeof defaultSideBySideRendererConfig;
|
||||
|
||||
constructor(hoganUtils: HoganJsUtils, config: SideBySideRendererConfig) {
|
||||
this.hoganUtils = hoganUtils;
|
||||
this.config = { ...defaultSideBySideRendererConfig, ...config };
|
||||
}
|
||||
|
||||
render(diffFiles: renderUtils.DiffFile[]): string | undefined {
|
||||
const content = diffFiles
|
||||
.map(file => {
|
||||
let diffs;
|
||||
if (file.blocks.length) {
|
||||
diffs = this.generateSideBySideFileHtml(file);
|
||||
} else {
|
||||
diffs = this.generateEmptyDiff();
|
||||
}
|
||||
|
||||
return this.makeDiffHtml(file, diffs);
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "wrapper", { content: content });
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
generateEmptyDiff(): FileHtml {
|
||||
return {
|
||||
right: "",
|
||||
left:
|
||||
this.hoganUtils.render(genericTemplatesPath, "empty-diff", {
|
||||
contentClass: "d2h-code-side-line",
|
||||
CSSLineClass: renderUtils.CSSLineClass
|
||||
}) || ""
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeDiffHtml(file: renderUtils.DiffFile, diffs: FileHtml): string {
|
||||
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, "file-diff");
|
||||
const filePathTemplate = this.hoganUtils.template(genericTemplatesPath, "file-path");
|
||||
const fileIconTemplate = this.hoganUtils.template(iconsBaseTemplatesPath, "file");
|
||||
const fileTagTemplate = this.hoganUtils.template(tagsBaseTemplatesPath, renderUtils.getFileIcon(file));
|
||||
|
||||
return fileDiffTemplate.render({
|
||||
file: file,
|
||||
fileHtmlId: renderUtils.getHtmlId(file),
|
||||
diffs: diffs,
|
||||
filePath: filePathTemplate.render(
|
||||
{
|
||||
fileDiffName: renderUtils.filenameDiff(file)
|
||||
},
|
||||
{
|
||||
fileIcon: fileIconTemplate,
|
||||
fileTag: fileTagTemplate
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
makeSideHtml(blockHeader: string): string {
|
||||
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
||||
CSSLineClass: renderUtils.CSSLineClass,
|
||||
blockHeader: utils.escapeForHtml(blockHeader),
|
||||
lineClass: "d2h-code-side-linenumber",
|
||||
contentClass: "d2h-code-side-line"
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
generateSideBySideFileHtml(file: renderUtils.DiffFile): FileHtml {
|
||||
const prefixSize = renderUtils.prefixLength(file.isCombined);
|
||||
const distance = Rematch.newDistanceFn((e: renderUtils.DiffLine) => e.content.substring(prefixSize));
|
||||
const matcher = Rematch.newMatcherFn(distance);
|
||||
|
||||
const fileHtml = {
|
||||
right: "",
|
||||
left: ""
|
||||
};
|
||||
|
||||
file.blocks.forEach(block => {
|
||||
fileHtml.left += this.makeSideHtml(block.header);
|
||||
fileHtml.right += this.makeSideHtml("");
|
||||
|
||||
let oldLines: renderUtils.DiffLine[] = [];
|
||||
let newLines: renderUtils.DiffLine[] = [];
|
||||
|
||||
const processChangeBlock = (): void => {
|
||||
let matches;
|
||||
let insertType: renderUtils.CSSLineClass;
|
||||
let deleteType: renderUtils.CSSLineClass;
|
||||
|
||||
const comparisons = oldLines.length * newLines.length;
|
||||
|
||||
const maxLineSizeInBlock = Math.max.apply(null, oldLines.concat(newLines).map(elem => elem.content.length));
|
||||
|
||||
const doMatching =
|
||||
comparisons < this.config.matchingMaxComparisons &&
|
||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||
(this.config.matching === "lines" || this.config.matching === "words");
|
||||
|
||||
if (doMatching) {
|
||||
matches = matcher(oldLines, newLines);
|
||||
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
|
||||
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
|
||||
} else {
|
||||
matches = [[oldLines, newLines]];
|
||||
insertType = renderUtils.CSSLineClass.INSERTS;
|
||||
deleteType = renderUtils.CSSLineClass.DELETES;
|
||||
}
|
||||
|
||||
matches.forEach(match => {
|
||||
oldLines = match[0];
|
||||
newLines = match[1];
|
||||
|
||||
const common = Math.min(oldLines.length, newLines.length);
|
||||
const max = Math.max(oldLines.length, newLines.length);
|
||||
|
||||
for (let j = 0; j < common; j++) {
|
||||
const oldLine = oldLines[j];
|
||||
const newLine = newLines[j];
|
||||
|
||||
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
|
||||
|
||||
fileHtml.left += this.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
deleteType,
|
||||
diff.oldLine.content,
|
||||
oldLine.oldNumber,
|
||||
diff.oldLine.prefix
|
||||
);
|
||||
fileHtml.right += this.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
insertType,
|
||||
diff.newLine.content,
|
||||
newLine.newNumber,
|
||||
diff.newLine.prefix
|
||||
);
|
||||
}
|
||||
|
||||
if (max > common) {
|
||||
const oldSlice = oldLines.slice(common);
|
||||
const newSlice = newLines.slice(common);
|
||||
|
||||
const tmpHtml = this.processLines(file.isCombined, oldSlice, newSlice);
|
||||
fileHtml.left += tmpHtml.left;
|
||||
fileHtml.right += tmpHtml.right;
|
||||
}
|
||||
});
|
||||
|
||||
oldLines = [];
|
||||
newLines = [];
|
||||
};
|
||||
|
||||
for (let i = 0; i < block.lines.length; i++) {
|
||||
const diffLine = block.lines[i];
|
||||
const { prefix, line } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
||||
const escapedLine = utils.escapeForHtml(line);
|
||||
|
||||
if (
|
||||
diffLine.type !== renderUtils.LineType.INSERT &&
|
||||
(newLines.length > 0 || (diffLine.type !== renderUtils.LineType.DELETE && oldLines.length > 0))
|
||||
) {
|
||||
processChangeBlock();
|
||||
}
|
||||
|
||||
if (diffLine.type === renderUtils.LineType.CONTEXT) {
|
||||
fileHtml.left += this.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
renderUtils.toCSSClass(diffLine.type),
|
||||
escapedLine,
|
||||
diffLine.oldNumber,
|
||||
prefix
|
||||
);
|
||||
fileHtml.right += this.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
renderUtils.toCSSClass(diffLine.type),
|
||||
escapedLine,
|
||||
diffLine.newNumber,
|
||||
prefix
|
||||
);
|
||||
} else if (diffLine.type === renderUtils.LineType.INSERT && !oldLines.length) {
|
||||
fileHtml.left += this.generateSingleLineHtml(file.isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
||||
fileHtml.right += this.generateSingleLineHtml(
|
||||
file.isCombined,
|
||||
renderUtils.toCSSClass(diffLine.type),
|
||||
escapedLine,
|
||||
diffLine.newNumber,
|
||||
prefix
|
||||
);
|
||||
} else if (diffLine.type === renderUtils.LineType.DELETE) {
|
||||
oldLines.push(diffLine);
|
||||
} else if (diffLine.type === renderUtils.LineType.INSERT && Boolean(oldLines.length)) {
|
||||
newLines.push(diffLine);
|
||||
} else {
|
||||
console.error("unknown state in html side-by-side generator");
|
||||
processChangeBlock();
|
||||
}
|
||||
}
|
||||
|
||||
processChangeBlock();
|
||||
});
|
||||
|
||||
return fileHtml;
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
processLines(isCombined: boolean, oldLines: renderUtils.DiffLine[], newLines: renderUtils.DiffLine[]): FileHtml {
|
||||
const fileHtml = {
|
||||
right: "",
|
||||
left: ""
|
||||
};
|
||||
|
||||
const maxLinesNumber = Math.max(oldLines.length, newLines.length);
|
||||
for (let i = 0; i < maxLinesNumber; i++) {
|
||||
const oldLine = oldLines[i];
|
||||
const newLine = newLines[i];
|
||||
|
||||
let oldContent;
|
||||
let newContent;
|
||||
let oldPrefix;
|
||||
let newPrefix;
|
||||
|
||||
if (oldLine) {
|
||||
const { prefix, line } = renderUtils.deconstructLine(oldLine.content, isCombined);
|
||||
oldContent = utils.escapeForHtml(line);
|
||||
oldPrefix = prefix;
|
||||
} else {
|
||||
oldContent = "";
|
||||
oldPrefix = "";
|
||||
}
|
||||
|
||||
if (newLine) {
|
||||
const { prefix, line } = renderUtils.deconstructLine(newLine.content, isCombined);
|
||||
newContent = utils.escapeForHtml(line);
|
||||
newPrefix = prefix;
|
||||
} else {
|
||||
newContent = "";
|
||||
oldPrefix = "";
|
||||
}
|
||||
|
||||
if (oldLine && newLine) {
|
||||
fileHtml.left += this.generateSingleLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(oldLine.type),
|
||||
oldContent,
|
||||
oldLine.oldNumber,
|
||||
oldPrefix
|
||||
);
|
||||
fileHtml.right += this.generateSingleLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(newLine.type),
|
||||
newContent,
|
||||
newLine.newNumber,
|
||||
newPrefix
|
||||
);
|
||||
} else if (oldLine) {
|
||||
fileHtml.left += this.generateSingleLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(oldLine.type),
|
||||
oldContent,
|
||||
oldLine.oldNumber,
|
||||
oldPrefix
|
||||
);
|
||||
fileHtml.right += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
||||
} else if (newLine) {
|
||||
fileHtml.left += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
||||
fileHtml.right += this.generateSingleLineHtml(
|
||||
isCombined,
|
||||
renderUtils.toCSSClass(newLine.type),
|
||||
newContent,
|
||||
newLine.newNumber,
|
||||
newPrefix
|
||||
);
|
||||
} else {
|
||||
console.error("How did it get here?");
|
||||
}
|
||||
}
|
||||
|
||||
return fileHtml;
|
||||
}
|
||||
|
||||
// TODO: Make this private after improving tests
|
||||
generateSingleLineHtml(
|
||||
isCombined: boolean,
|
||||
type: renderUtils.CSSLineClass,
|
||||
content: string,
|
||||
number?: number,
|
||||
possiblePrefix?: string
|
||||
): string {
|
||||
let lineWithoutPrefix = content;
|
||||
let prefix = possiblePrefix;
|
||||
let lineClass = "d2h-code-side-linenumber";
|
||||
let contentClass = "d2h-code-side-line";
|
||||
let preparedType: string = type;
|
||||
|
||||
if (!number && !content) {
|
||||
lineClass += " d2h-code-side-emptyplaceholder";
|
||||
contentClass += " d2h-code-side-emptyplaceholder";
|
||||
preparedType += " d2h-emptyplaceholder";
|
||||
prefix = " ";
|
||||
lineWithoutPrefix = " ";
|
||||
} else if (!prefix) {
|
||||
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
|
||||
prefix = lineWithPrefix.prefix;
|
||||
lineWithoutPrefix = lineWithPrefix.line;
|
||||
}
|
||||
|
||||
if (prefix === " ") {
|
||||
prefix = " ";
|
||||
}
|
||||
|
||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||
type: preparedType,
|
||||
lineClass: lineClass,
|
||||
contentClass: contentClass,
|
||||
prefix: prefix,
|
||||
content: lineWithoutPrefix,
|
||||
lineNumber: number
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<tr>
|
||||
<td class="{{lineClass}} {{diffParser.LINE_TYPE.INFO}}"></td>
|
||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">{{{blockHeader}}}</div>
|
||||
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}} {{CSSLineClass.INFO}}">{{{blockHeader}}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<tr>
|
||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">
|
||||
<td class="{{CSSLineClass.INFO}}">
|
||||
<div class="{{contentClass}} {{CSSLineClass.INFO}}">
|
||||
File without changes
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Diff to HTML (diff2html-ui.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
* Depends on: [ jQuery ]
|
||||
* Optional dependencies on: [ highlight.js ]
|
||||
*
|
||||
*/
|
||||
|
||||
/* global $, hljs, Diff2Html */
|
||||
|
||||
(function() {
|
||||
const highlightJS = require("./highlight.js-internals.js").HighlightJS;
|
||||
|
||||
let diffJson = null;
|
||||
const defaultTarget = "body";
|
||||
let currentSelectionColumnId = -1;
|
||||
|
||||
function Diff2HtmlUI(config) {
|
||||
const cfg = config || {};
|
||||
|
||||
if (cfg.diff) {
|
||||
diffJson = Diff2Html.getJsonFromDiff(cfg.diff);
|
||||
} else if (cfg.json) {
|
||||
diffJson = cfg.json;
|
||||
}
|
||||
|
||||
this._initSelection();
|
||||
}
|
||||
|
||||
Diff2HtmlUI.prototype.draw = function(targetId, config) {
|
||||
const cfg = config || {};
|
||||
cfg.inputFormat = "json";
|
||||
const $target = this._getTarget(targetId);
|
||||
$target.html(Diff2Html.getPrettyHtml(diffJson, cfg));
|
||||
|
||||
if (cfg.synchronisedScroll) {
|
||||
this.synchronisedScroll($target, cfg);
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.synchronisedScroll = function(targetId) {
|
||||
const $target = this._getTarget(targetId);
|
||||
$target.find(".d2h-file-side-diff").scroll(function() {
|
||||
const $this = $(this);
|
||||
$this
|
||||
.closest(".d2h-file-wrapper")
|
||||
.find(".d2h-file-side-diff")
|
||||
.scrollLeft($this.scrollLeft());
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.fileListCloseable = function(targetId, startVisible) {
|
||||
const $target = this._getTarget(targetId);
|
||||
|
||||
const hashTag = this._getHashTag();
|
||||
|
||||
const $showBtn = $target.find(".d2h-show");
|
||||
const $hideBtn = $target.find(".d2h-hide");
|
||||
const $fileList = $target.find(".d2h-file-list");
|
||||
|
||||
if (hashTag === "files-summary-show") show();
|
||||
else if (hashTag === "files-summary-hide") hide();
|
||||
else if (startVisible) show();
|
||||
else hide();
|
||||
|
||||
$showBtn.click(show);
|
||||
$hideBtn.click(hide);
|
||||
|
||||
function show() {
|
||||
$showBtn.hide();
|
||||
$hideBtn.show();
|
||||
$fileList.show();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
$hideBtn.hide();
|
||||
$showBtn.show();
|
||||
$fileList.hide();
|
||||
}
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype.highlightCode = function(targetId) {
|
||||
const that = this;
|
||||
|
||||
const $target = that._getTarget(targetId);
|
||||
|
||||
// collect all the diff files and execute the highlight on their lines
|
||||
const $files = $target.find(".d2h-file-wrapper");
|
||||
$files.map(function(_i, file) {
|
||||
let oldLinesState;
|
||||
let newLinesState;
|
||||
const $file = $(file);
|
||||
const language = $file.data("lang");
|
||||
|
||||
// collect all the code lines and execute the highlight on them
|
||||
const $codeLines = $file.find(".d2h-code-line-ctn");
|
||||
$codeLines.map(function(_j, line) {
|
||||
const $line = $(line);
|
||||
const text = line.textContent;
|
||||
const lineParent = line.parentNode;
|
||||
|
||||
let lineState;
|
||||
if (lineParent.className.indexOf("d2h-del") !== -1) {
|
||||
lineState = oldLinesState;
|
||||
} else {
|
||||
lineState = newLinesState;
|
||||
}
|
||||
|
||||
const result = hljs.getLanguage(language)
|
||||
? hljs.highlight(language, text, true, lineState)
|
||||
: hljs.highlightAuto(text);
|
||||
|
||||
if (lineParent.className.indexOf("d2h-del") !== -1) {
|
||||
oldLinesState = result.top;
|
||||
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
|
||||
newLinesState = result.top;
|
||||
} else {
|
||||
oldLinesState = result.top;
|
||||
newLinesState = result.top;
|
||||
}
|
||||
|
||||
const originalStream = highlightJS.nodeStream(line);
|
||||
if (originalStream.length) {
|
||||
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
|
||||
resultNode.innerHTML = result.value;
|
||||
result.value = highlightJS.mergeStreams(originalStream, highlightJS.nodeStream(resultNode), text);
|
||||
}
|
||||
|
||||
$line.addClass("hljs");
|
||||
$line.addClass(result.language);
|
||||
$line.html(result.value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getTarget = function(targetId) {
|
||||
let $target;
|
||||
|
||||
if (typeof targetId === "object" && targetId instanceof jQuery) {
|
||||
$target = targetId;
|
||||
} else if (typeof targetId === "string") {
|
||||
$target = $(targetId);
|
||||
} else {
|
||||
console.error("Wrong target provided! Falling back to default value 'body'.");
|
||||
console.log("Please provide a jQuery object or a valid DOM query string.");
|
||||
$target = $(defaultTarget);
|
||||
}
|
||||
|
||||
return $target;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getHashTag = function() {
|
||||
const docUrl = document.URL;
|
||||
const hashTagIndex = docUrl.indexOf("#");
|
||||
|
||||
let hashTag = null;
|
||||
if (hashTagIndex !== -1) {
|
||||
hashTag = docUrl.substr(hashTagIndex + 1);
|
||||
}
|
||||
|
||||
return hashTag;
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._distinct = function(collection) {
|
||||
return collection.filter(function(v, i) {
|
||||
return collection.indexOf(v) === i;
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._initSelection = function() {
|
||||
const body = $("body");
|
||||
const that = this;
|
||||
|
||||
body.on("mousedown", ".d2h-diff-table", function(event) {
|
||||
const target = $(event.target);
|
||||
const table = target.closest(".d2h-diff-table");
|
||||
|
||||
if (target.closest(".d2h-code-line,.d2h-code-side-line").length) {
|
||||
table.removeClass("selecting-left");
|
||||
table.addClass("selecting-right");
|
||||
currentSelectionColumnId = 1;
|
||||
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber").length) {
|
||||
table.removeClass("selecting-right");
|
||||
table.addClass("selecting-left");
|
||||
currentSelectionColumnId = 0;
|
||||
}
|
||||
});
|
||||
|
||||
body.on("copy", ".d2h-diff-table", function(event) {
|
||||
const clipboardData = event.originalEvent.clipboardData;
|
||||
const text = that._getSelectedText();
|
||||
clipboardData.setData("text", text);
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
Diff2HtmlUI.prototype._getSelectedText = function() {
|
||||
const sel = window.getSelection();
|
||||
const range = sel.getRangeAt(0);
|
||||
const doc = range.cloneContents();
|
||||
const nodes = doc.querySelectorAll("tr");
|
||||
let text = "";
|
||||
const idx = currentSelectionColumnId;
|
||||
|
||||
if (nodes.length === 0) {
|
||||
text = doc.textContent;
|
||||
} else {
|
||||
[].forEach.call(nodes, function(tr, i) {
|
||||
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
|
||||
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
module.exports.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
||||
// Expose diff2html in the browser
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
})();
|
||||
220
src/ui/js/diff2html-ui.ts
Normal file
220
src/ui/js/diff2html-ui.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import HighlightJS from "highlight.js";
|
||||
import * as HighlightJSInternals from "./highlight.js-internals";
|
||||
import { html, Diff2HtmlConfig, defaultDiff2HtmlConfig } from "../../diff2html";
|
||||
import { DiffFile } from "../../render-utils";
|
||||
|
||||
interface Diff2HtmlUIConfig extends Diff2HtmlConfig {
|
||||
synchronisedScroll?: boolean;
|
||||
}
|
||||
|
||||
const defaultDiff2HtmlUIConfig = {
|
||||
...defaultDiff2HtmlConfig,
|
||||
synchronisedScroll: true
|
||||
};
|
||||
|
||||
export default class Diff2HtmlUI {
|
||||
readonly config: typeof defaultDiff2HtmlUIConfig;
|
||||
readonly diffHtml: string;
|
||||
targetElement: HTMLElement;
|
||||
currentSelectionColumnId = -1;
|
||||
|
||||
constructor(diffInput: string | DiffFile[], target: HTMLElement, config: Diff2HtmlUIConfig = {}) {
|
||||
this.config = { ...defaultDiff2HtmlUIConfig, ...config };
|
||||
this.diffHtml = html(diffInput, this.config);
|
||||
this.targetElement = target;
|
||||
}
|
||||
|
||||
draw(): void {
|
||||
this.targetElement.innerHTML = this.diffHtml;
|
||||
this.initSelection();
|
||||
if (this.config.synchronisedScroll) this.synchronisedScroll();
|
||||
}
|
||||
|
||||
synchronisedScroll(): void {
|
||||
this.targetElement.querySelectorAll(".d2h-file-wrapper").forEach(wrapper => {
|
||||
const [left, right] = [].slice.call(wrapper.querySelectorAll(".d2h-file-side-diff")) as HTMLElement[];
|
||||
|
||||
if (left === undefined || right === undefined) return;
|
||||
|
||||
const onScroll = (event: Event): void => {
|
||||
if (event === null || event.target === null) return;
|
||||
|
||||
if (event.target === left) {
|
||||
right.scrollTop = left.scrollTop;
|
||||
} else {
|
||||
left.scrollTop = right.scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
left.addEventListener("scroll", onScroll);
|
||||
right.addEventListener("scroll", onScroll);
|
||||
});
|
||||
}
|
||||
|
||||
fileListCloseable(startVisible: boolean): void {
|
||||
const hashTag = this.getHashTag();
|
||||
|
||||
const showBtn = this.targetElement.querySelector(".d2h-show") as HTMLElement;
|
||||
const hideBtn = this.targetElement.querySelector(".d2h-hide") as HTMLElement;
|
||||
const fileList = this.targetElement.querySelector(".d2h-file-list") as HTMLElement;
|
||||
|
||||
if (showBtn === null || hideBtn === null || fileList === null) return;
|
||||
|
||||
function show(): void {
|
||||
showBtn.style.display = "";
|
||||
hideBtn.style.display = "";
|
||||
fileList.style.display = "";
|
||||
}
|
||||
|
||||
function hide(): void {
|
||||
showBtn.style.display = "none";
|
||||
hideBtn.style.display = "none";
|
||||
fileList.style.display = "none";
|
||||
}
|
||||
|
||||
showBtn.addEventListener("click", () => show());
|
||||
hideBtn.addEventListener("click", () => hide());
|
||||
|
||||
if (hashTag === "files-summary-show") show();
|
||||
else if (hashTag === "files-summary-hide") hide();
|
||||
else if (startVisible) show();
|
||||
else hide();
|
||||
}
|
||||
|
||||
highlightCode(): void {
|
||||
// Collect all the diff files and execute the highlight on their lines
|
||||
const files = this.targetElement.querySelectorAll(".d2h-file-wrapper");
|
||||
files.forEach(file => {
|
||||
let oldLinesState: HighlightJS.ICompiledMode;
|
||||
let newLinesState: HighlightJS.ICompiledMode;
|
||||
|
||||
// Collect all the code lines and execute the highlight on them
|
||||
const codeLines = file.querySelectorAll(".d2h-code-line-ctn");
|
||||
codeLines.forEach(line => {
|
||||
const text = line.textContent;
|
||||
const lineParent = line.parentNode as HTMLElement;
|
||||
|
||||
if (lineParent === null || text === null) return;
|
||||
|
||||
const lineState = lineParent.className.indexOf("d2h-del") !== -1 ? oldLinesState : newLinesState;
|
||||
|
||||
const language = file.getAttribute("data-lang");
|
||||
const result =
|
||||
language && HighlightJS.getLanguage(language)
|
||||
? HighlightJS.highlight(language, text, true, lineState)
|
||||
: HighlightJS.highlightAuto(text);
|
||||
|
||||
if (this.instanceOfIHighlightResult(result)) {
|
||||
if (lineParent.className.indexOf("d2h-del") !== -1) {
|
||||
oldLinesState = result.top;
|
||||
} else if (lineParent.className.indexOf("d2h-ins") !== -1) {
|
||||
newLinesState = result.top;
|
||||
} else {
|
||||
oldLinesState = result.top;
|
||||
newLinesState = result.top;
|
||||
}
|
||||
}
|
||||
|
||||
const originalStream = HighlightJSInternals.nodeStream(line);
|
||||
if (originalStream.length) {
|
||||
const resultNode = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
|
||||
resultNode.innerHTML = result.value;
|
||||
result.value = HighlightJSInternals.mergeStreams(
|
||||
originalStream,
|
||||
HighlightJSInternals.nodeStream(resultNode),
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
line.classList.add("hljs");
|
||||
line.classList.add("result.language");
|
||||
line.innerHTML = result.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private instanceOfIHighlightResult(
|
||||
object: HighlightJS.IHighlightResult | HighlightJS.IAutoHighlightResult
|
||||
): object is HighlightJS.IHighlightResult {
|
||||
return "top" in object;
|
||||
}
|
||||
|
||||
private getHashTag(): string | null {
|
||||
const docUrl = document.URL;
|
||||
const hashTagIndex = docUrl.indexOf("#");
|
||||
|
||||
let hashTag = null;
|
||||
if (hashTagIndex !== -1) {
|
||||
hashTag = docUrl.substr(hashTagIndex + 1);
|
||||
}
|
||||
|
||||
return hashTag;
|
||||
}
|
||||
|
||||
private initSelection(): void {
|
||||
const body = document.getElementsByTagName("body")[0];
|
||||
const diffTable = body.getElementsByClassName("d2h-diff-table")[0];
|
||||
|
||||
diffTable.addEventListener("mousedown", event => {
|
||||
if (event === null || event.target === null) return;
|
||||
|
||||
const mouseEvent = event as MouseEvent;
|
||||
const target = mouseEvent.target as HTMLElement;
|
||||
const table = target.closest(".d2h-diff-table");
|
||||
|
||||
if (table !== null) {
|
||||
if (target.closest(".d2h-code-line,.d2h-code-side-line") !== null) {
|
||||
table.classList.remove("selecting-left");
|
||||
table.classList.add("selecting-right");
|
||||
this.currentSelectionColumnId = 1;
|
||||
} else if (target.closest(".d2h-code-linenumber,.d2h-code-side-linenumber") !== null) {
|
||||
table.classList.remove("selecting-right");
|
||||
table.classList.add("selecting-left");
|
||||
this.currentSelectionColumnId = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
diffTable.addEventListener("copy", event => {
|
||||
const clipboardEvent = event as ClipboardEvent;
|
||||
const clipboardData = clipboardEvent.clipboardData;
|
||||
const text = this.getSelectedText();
|
||||
|
||||
if (clipboardData === null || text === undefined) return;
|
||||
|
||||
clipboardData.setData("text", text);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
private getSelectedText(): string | undefined {
|
||||
const sel = window.getSelection();
|
||||
|
||||
if (sel === null) return;
|
||||
|
||||
const range = sel.getRangeAt(0);
|
||||
const doc = range.cloneContents();
|
||||
const nodes = doc.querySelectorAll("tr");
|
||||
const idx = this.currentSelectionColumnId;
|
||||
|
||||
let text = "";
|
||||
if (nodes.length === 0) {
|
||||
text = doc.textContent || "";
|
||||
} else {
|
||||
nodes.forEach((tr, i) => {
|
||||
const td = tr.cells[tr.cells.length === 1 ? 0 : idx];
|
||||
|
||||
if (td === null || td.textContent === null) return;
|
||||
|
||||
text += (i ? "\n" : "") + td.textContent.replace(/(?:\r\n|\r|\n)/g, "");
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Avoid disabling types
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
global.Diff2HtmlUI = Diff2HtmlUI;
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* highlight.js
|
||||
* Author: isagalaev
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
function HighlightJS() {}
|
||||
|
||||
/*
|
||||
* Copied from Highlight.js Private API
|
||||
* Will be removed when this part of the API is exposed
|
||||
*/
|
||||
|
||||
/* Utility vars */
|
||||
|
||||
const ArrayProto = [];
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
function escape(value) {
|
||||
return value
|
||||
.replace(/&/gm, "&")
|
||||
.replace(/</gm, "<")
|
||||
.replace(/>/gm, ">");
|
||||
}
|
||||
|
||||
function tag(node) {
|
||||
return node.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
/* Stream merging */
|
||||
|
||||
HighlightJS.prototype.nodeStream = function(node) {
|
||||
const result = [];
|
||||
(function _nodeStream(node, offset) {
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType === 3) {
|
||||
offset += child.nodeValue.length;
|
||||
} else if (child.nodeType === 1) {
|
||||
result.push({
|
||||
event: "start",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
offset = _nodeStream(child, offset);
|
||||
// Prevent void elements from having an end tag that would actually
|
||||
// double them in the output. There are more void elements in HTML
|
||||
// but we list only those realistically expected in code display.
|
||||
if (!tag(child).match(/br|hr|img|input/)) {
|
||||
result.push({
|
||||
event: "stop",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
})(node, 0);
|
||||
return result;
|
||||
};
|
||||
|
||||
HighlightJS.prototype.mergeStreams = function(original, highlighted, value) {
|
||||
let processed = 0;
|
||||
let result = "";
|
||||
const nodeStack = [];
|
||||
|
||||
function selectStream() {
|
||||
if (!original.length || !highlighted.length) {
|
||||
return original.length ? original : highlighted;
|
||||
}
|
||||
if (original[0].offset !== highlighted[0].offset) {
|
||||
return original[0].offset < highlighted[0].offset ? original : highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid starting the stream just before it should stop the order is
|
||||
ensured that original always starts first and closes last:
|
||||
if (event1 == 'start' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'start' && event2 == 'stop')
|
||||
return highlighted;
|
||||
if (event1 == 'stop' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'stop' && event2 == 'stop')
|
||||
return highlighted;
|
||||
... which is collapsed to:
|
||||
*/
|
||||
return highlighted[0].event === "start" ? original : highlighted;
|
||||
}
|
||||
|
||||
function open(node) {
|
||||
function attr_str(a) {
|
||||
return " " + a.nodeName + '="' + escape(a.value) + '"';
|
||||
}
|
||||
|
||||
result += "<" + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join("") + ">";
|
||||
}
|
||||
|
||||
function close(node) {
|
||||
result += "</" + tag(node) + ">";
|
||||
}
|
||||
|
||||
function render(event) {
|
||||
(event.event === "start" ? open : close)(event.node);
|
||||
}
|
||||
|
||||
while (original.length || highlighted.length) {
|
||||
let stream = selectStream();
|
||||
result += escape(value.substring(processed, stream[0].offset));
|
||||
processed = stream[0].offset;
|
||||
if (stream === original) {
|
||||
/*
|
||||
On any opening or closing tag of the original markup we first close
|
||||
the entire highlighted node stack, then render the original tag along
|
||||
with all the following original tags at the same offset and then
|
||||
reopen all the tags on the highlighted stack.
|
||||
*/
|
||||
nodeStack.reverse().forEach(close);
|
||||
do {
|
||||
render(stream.splice(0, 1)[0]);
|
||||
stream = selectStream();
|
||||
} while (stream === original && stream.length && stream[0].offset === processed);
|
||||
nodeStack.reverse().forEach(open);
|
||||
} else {
|
||||
if (stream[0].event === "start") {
|
||||
nodeStack.push(stream[0].node);
|
||||
} else {
|
||||
nodeStack.pop();
|
||||
}
|
||||
render(stream.splice(0, 1)[0]);
|
||||
}
|
||||
}
|
||||
return result + escape(value.substr(processed));
|
||||
};
|
||||
|
||||
/* **** Highlight.js Private API **** */
|
||||
|
||||
module.exports.HighlightJS = new HighlightJS();
|
||||
})();
|
||||
134
src/ui/js/highlight.js-internals.ts
Normal file
134
src/ui/js/highlight.js-internals.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copied from Highlight.js Private API
|
||||
* Will be removed when this part of the API is exposed
|
||||
*/
|
||||
|
||||
/* Utility functions */
|
||||
|
||||
function escape(value: string): string {
|
||||
return value
|
||||
.replace(/&/gm, "&")
|
||||
.replace(/</gm, "<")
|
||||
.replace(/>/gm, ">");
|
||||
}
|
||||
|
||||
function tag(node: Node): string {
|
||||
return node.nodeName.toLowerCase();
|
||||
}
|
||||
|
||||
/* Stream merging */
|
||||
|
||||
type NodeEvent = {
|
||||
event: "start" | "stop";
|
||||
offset: number;
|
||||
node: Node;
|
||||
};
|
||||
|
||||
export function nodeStream(node: Node): NodeEvent[] {
|
||||
const result: NodeEvent[] = [];
|
||||
|
||||
const nodeStream = (node: Node, offset: number): number => {
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.nodeType === 3 && child.nodeValue !== null) {
|
||||
offset += child.nodeValue.length;
|
||||
} else if (child.nodeType === 1) {
|
||||
result.push({
|
||||
event: "start",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
offset = nodeStream(child, offset);
|
||||
// Prevent void elements from having an end tag that would actually
|
||||
// double them in the output. There are more void elements in HTML
|
||||
// but we list only those realistically expected in code display.
|
||||
if (!tag(child).match(/br|hr|img|input/)) {
|
||||
result.push({
|
||||
event: "stop",
|
||||
offset: offset,
|
||||
node: child
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
nodeStream(node, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mergeStreams(original: NodeEvent[], highlighted: NodeEvent[], value: string): string {
|
||||
let processed = 0;
|
||||
let result = "";
|
||||
const nodeStack = [];
|
||||
|
||||
function selectStream(): NodeEvent[] {
|
||||
if (!original.length || !highlighted.length) {
|
||||
return original.length ? original : highlighted;
|
||||
}
|
||||
if (original[0].offset !== highlighted[0].offset) {
|
||||
return original[0].offset < highlighted[0].offset ? original : highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid starting the stream just before it should stop the order is
|
||||
ensured that original always starts first and closes last:
|
||||
if (event1 == 'start' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'start' && event2 == 'stop')
|
||||
return highlighted;
|
||||
if (event1 == 'stop' && event2 == 'start')
|
||||
return original;
|
||||
if (event1 == 'stop' && event2 == 'stop')
|
||||
return highlighted;
|
||||
... which is collapsed to:
|
||||
*/
|
||||
return highlighted[0].event === "start" ? original : highlighted;
|
||||
}
|
||||
|
||||
function open(node: Node): void {
|
||||
const htmlNode = node as HTMLElement;
|
||||
result += `<${tag(node)} ${[].map
|
||||
.call(htmlNode.attributes, (attr: Attr) => `${attr.nodeName}="${escape(attr.value)}"`)
|
||||
.join(" ")}>`;
|
||||
}
|
||||
|
||||
function close(node: Node): void {
|
||||
result += "</" + tag(node) + ">";
|
||||
}
|
||||
|
||||
function render(event: NodeEvent): void {
|
||||
(event.event === "start" ? open : close)(event.node);
|
||||
}
|
||||
|
||||
while (original.length || highlighted.length) {
|
||||
let stream = selectStream();
|
||||
result += escape(value.substring(processed, stream[0].offset));
|
||||
processed = stream[0].offset;
|
||||
if (stream === original) {
|
||||
/*
|
||||
On any opening or closing tag of the original markup we first close
|
||||
the entire highlighted node stack, then render the original tag along
|
||||
with all the following original tags at the same offset and then
|
||||
reopen all the tags on the highlighted stack.
|
||||
*/
|
||||
nodeStack.reverse().forEach(close);
|
||||
do {
|
||||
render(stream.splice(0, 1)[0]);
|
||||
stream = selectStream();
|
||||
} while (stream === original && stream.length && stream[0].offset === processed);
|
||||
nodeStack.reverse().forEach(open);
|
||||
} else {
|
||||
if (stream[0].event === "start") {
|
||||
nodeStack.push(stream[0].node);
|
||||
} else {
|
||||
nodeStack.pop();
|
||||
}
|
||||
render(stream.splice(0, 1)[0]);
|
||||
}
|
||||
}
|
||||
return result + escape(value.substr(processed));
|
||||
}
|
||||
|
||||
/* **** Highlight.js Private API **** */
|
||||
48
src/utils.js
48
src/utils.js
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Utils (utils.js)
|
||||
* Author: rtfpessoa
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const merge = require("merge");
|
||||
|
||||
function Utils() {}
|
||||
|
||||
Utils.prototype.escape = function(str) {
|
||||
return str
|
||||
.slice(0)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\//g, "/");
|
||||
};
|
||||
|
||||
Utils.prototype.startsWith = function(str, start) {
|
||||
if (typeof start === "object") {
|
||||
let result = false;
|
||||
start.forEach(function(s) {
|
||||
if (str.indexOf(s) === 0) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return str && str.indexOf(start) === 0;
|
||||
};
|
||||
|
||||
Utils.prototype.valueOrEmpty = function(value) {
|
||||
return value || "";
|
||||
};
|
||||
|
||||
Utils.prototype.safeConfig = function(cfg, defaultConfig) {
|
||||
return merge.recursive(true, defaultConfig, cfg);
|
||||
};
|
||||
|
||||
module.exports.Utils = new Utils();
|
||||
})();
|
||||
68
src/utils.ts
Normal file
68
src/utils.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const specials = [
|
||||
// Order matters for these
|
||||
"-",
|
||||
"[",
|
||||
"]",
|
||||
// Order doesn't matter for any of these
|
||||
"/",
|
||||
"{",
|
||||
"}",
|
||||
"(",
|
||||
")",
|
||||
"*",
|
||||
"+",
|
||||
"?",
|
||||
".",
|
||||
"\\",
|
||||
"^",
|
||||
"$",
|
||||
"|"
|
||||
];
|
||||
|
||||
// All characters will be escaped with '\'
|
||||
// even though only some strictly require it when inside of []
|
||||
const regex = RegExp("[" + specials.join("\\") + "]", "g");
|
||||
|
||||
/**
|
||||
* Escapes all required characters for safe usage inside a RegExp
|
||||
*/
|
||||
export function escapeForRegExp(str: string): string {
|
||||
return str.replace(regex, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all required characters for safe HTML rendering
|
||||
*/
|
||||
export function escapeForHtml(str: string): string {
|
||||
return str
|
||||
.slice(0)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\//g, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all '\' in @path to unix style '/'
|
||||
*/
|
||||
export function unifyPath(path: string): string {
|
||||
return path ? path.replace("\\", "/") : path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unique number identifier for @text
|
||||
*/
|
||||
export function hashCode(text: string): number {
|
||||
let i, chr, len;
|
||||
let hash = 0;
|
||||
|
||||
for (i = 0, len = text.length; i < len; i++) {
|
||||
chr = text.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es5", "es6", "es7", "esnext", "dom", "dom.iterable"],
|
||||
// TODO: Change to true after migration to TS is complete
|
||||
"allowJs": true,
|
||||
"declaration": false,
|
||||
|
|
@ -21,8 +22,9 @@
|
|||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"include": ["./src/**/*", "./typings/**/*"],
|
||||
"exclude": ["node_modules", "./src/__tests__/*"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@
|
|||
"declarationMap": false,
|
||||
"sourceMap": false
|
||||
},
|
||||
"include": ["./scripts/**/*"]
|
||||
"include": ["./scripts/**/*", "./typings/**/*"]
|
||||
}
|
||||
|
|
|
|||
93
typings/hoganjs.d.ts
vendored
Normal file
93
typings/hoganjs.d.ts
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Type definitions for hogan.js 3.0
|
||||
// Project: http://twitter.github.com/hogan.js/
|
||||
// Definitions by: Andrew Leedham <https://github.com/AndrewLeedham>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.2
|
||||
|
||||
declare module "hogan.js" {
|
||||
export interface Context {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface SectionTags {
|
||||
o: string;
|
||||
c: string;
|
||||
}
|
||||
|
||||
export interface HoganOptions {
|
||||
asString?: boolean;
|
||||
sectionTags?: ReadonlyArray<SectionTags>;
|
||||
delimiters?: string;
|
||||
disableLambda?: boolean;
|
||||
}
|
||||
|
||||
export interface Token {
|
||||
tag: string;
|
||||
otag?: string;
|
||||
ctag?: string;
|
||||
i?: number;
|
||||
n?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface Leaf extends Token {
|
||||
end: number;
|
||||
nodes: Token[];
|
||||
}
|
||||
|
||||
export type Tree = Leaf[];
|
||||
|
||||
export interface Partials {
|
||||
[symbol: string]: HoganTemplate;
|
||||
}
|
||||
|
||||
export interface HoganConstructor {
|
||||
code: (context: any, partials: object, indent: string) => string;
|
||||
partials: object;
|
||||
subs: object;
|
||||
}
|
||||
|
||||
export class HoganTemplate {
|
||||
constructor(codeObject: HoganConstructor);
|
||||
|
||||
/**
|
||||
* Renders the template to a string.
|
||||
*
|
||||
* @param context - The data to render the template with.
|
||||
* @param partials - The partials to render the template with.
|
||||
* @param indent - The string to indent when rendering the template.
|
||||
* @returns A rendered template.
|
||||
*/
|
||||
render(context: Context, partials?: Partials, indent?: string): string;
|
||||
}
|
||||
|
||||
export { HoganTemplate as Template, HoganTemplate as template };
|
||||
|
||||
export function compile(text: string, options?: HoganOptions & { asString: false }): HoganTemplate;
|
||||
export function compile(text: string, options?: HoganOptions & { asString: true }): string;
|
||||
/**
|
||||
* Compiles templates to HoganTemplate objects, which have a render method.
|
||||
*
|
||||
* @param text - Raw mustache string to compile.
|
||||
* @param options - Options to use when compiling. See https://github.com/twitter/hogan.js#compilation-options.
|
||||
* @returns A HoganTemplate.
|
||||
*/
|
||||
export function compile(text: string, options?: HoganOptions): HoganTemplate | string;
|
||||
/**
|
||||
* Scans templates returning an array of found tokens.
|
||||
*
|
||||
* @param text - Raw mustache string to scan.
|
||||
* @param delimiters - A string that overrides the default delimiters. Example: "<% %>".
|
||||
* @returns Found tokens.
|
||||
*/
|
||||
export function scan(text: string, delimiters?: string): Token[];
|
||||
/**
|
||||
* Structures tokens into a tree.
|
||||
*
|
||||
* @param tokens - An array of scanned tokens.
|
||||
* @param text - Unused pass undefined.
|
||||
* @param options - Options to use when parsing. See https://github.com/twitter/hogan.js#compilation-options.
|
||||
* @returns The tree structure of the given tokens.
|
||||
*/
|
||||
export function parse(tokens: ReadonlyArray<Token>, text?: undefined, options?: HoganOptions): Tree;
|
||||
}
|
||||
3
typings/merge.d.ts
vendored
Normal file
3
typings/merge.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare module "merge" {
|
||||
export function recursive(clone: boolean, ...items: object[]): object;
|
||||
}
|
||||
|
|
@ -1,8 +1,4 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/scala.min.js"></script>
|
||||
|
||||
<!-- diff2html -->
|
||||
<script type="text/javascript" src="assets/diff2html.min.js"></script>
|
||||
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
|
||||
<!-- -->
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* global Diff2HtmlUI */
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
|
||||
/*
|
||||
* Example URLs:
|
||||
|
|
@ -13,227 +14,229 @@
|
|||
* https://bitbucket.org/atlassian/amps/pull-requests/236
|
||||
*/
|
||||
|
||||
$(document).ready(function() {
|
||||
const searchParam = "diff";
|
||||
|
||||
function getUrlFromSearch(search) {
|
||||
try {
|
||||
return search
|
||||
.split("?")[1]
|
||||
.split(searchParam + "=")[1]
|
||||
.split("&")[0];
|
||||
} catch (_ignore) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParamsFromSearch(search) {
|
||||
const map = new Map();
|
||||
try {
|
||||
search
|
||||
.split("?")[1]
|
||||
.split("&")
|
||||
.forEach(e => {
|
||||
const values = e.split("=");
|
||||
map.set(values[0], values[1]);
|
||||
});
|
||||
} catch (_ignore) {}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function prepareUrl(url) {
|
||||
let fetchUrl;
|
||||
const headers = new Headers();
|
||||
|
||||
const githubCommitUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const gitlabCommitUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const gitlabPrUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
|
||||
const bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
|
||||
|
||||
function gitLabUrlGen(userName, projectName, type, value) {
|
||||
return (
|
||||
"https://crossorigin.me/https://gitlab.com/" + userName + "/" + projectName + "/" + type + "/" + value + ".diff"
|
||||
);
|
||||
}
|
||||
|
||||
function gitHubUrlGen(userName, projectName, type, value) {
|
||||
headers.append("Accept", "application/vnd.github.v3.diff");
|
||||
return "https://api.github.com/repos/" + userName + "/" + projectName + "/" + type + "/" + value;
|
||||
}
|
||||
|
||||
function bitbucketUrlGen(userName, projectName, type, value) {
|
||||
const baseUrl = "https://bitbucket.org/api/2.0/repositories/";
|
||||
if (type === "pullrequests") {
|
||||
return baseUrl + userName + "/" + projectName + "/pullrequests/" + value + "/diff";
|
||||
}
|
||||
return baseUrl + userName + "/" + projectName + "/diff/" + value;
|
||||
}
|
||||
|
||||
let values;
|
||||
if ((values = githubCommitUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "commits", values[3]);
|
||||
} else if ((values = githubPrUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "pulls", values[3]);
|
||||
} else if ((values = gitlabCommitUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = gitlabPrUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "merge_requests", values[3]);
|
||||
} else if ((values = bitbucketCommitUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = bitbucketPrUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "pullrequests", values[3]);
|
||||
} else {
|
||||
console.info("Could not parse url, using the provided url.");
|
||||
fetchUrl = "https://crossorigin.me/" + url;
|
||||
}
|
||||
|
||||
return {
|
||||
originalUrl: url,
|
||||
url: fetchUrl,
|
||||
headers: headers
|
||||
};
|
||||
}
|
||||
|
||||
function validateUrl(url) {
|
||||
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
function updateUrl(url) {
|
||||
const params = getParamsFromSearch(window.location.search);
|
||||
|
||||
if (params[searchParam] === url) return;
|
||||
|
||||
params[searchParam] = url;
|
||||
|
||||
const paramString = Object.keys(params)
|
||||
.map(function(k) {
|
||||
return k + "=" + params[k];
|
||||
})
|
||||
.join("&");
|
||||
|
||||
window.location = "demo.html?" + paramString;
|
||||
}
|
||||
|
||||
function draw(req, forced, elements) {
|
||||
if (!validateUrl(req.url)) {
|
||||
console.error("Invalid url provided!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
|
||||
|
||||
const outputFormat = elements.outputFormat.val();
|
||||
const showFiles = elements.showFiles.is(":checked");
|
||||
const matching = elements.matching.val();
|
||||
const wordsThreshold = elements.wordsThreshold.val();
|
||||
const matchingMaxComparisons = elements.matchingMaxComparisons.val();
|
||||
|
||||
fetch(req.url, {
|
||||
method: "GET",
|
||||
headers: req.headers,
|
||||
mode: "cors",
|
||||
cache: "default"
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text();
|
||||
})
|
||||
.then(function(data) {
|
||||
const params = getParamsFromSearch(window.location.search);
|
||||
delete params[searchParam];
|
||||
|
||||
if (forced) {
|
||||
params.outputFormat = outputFormat;
|
||||
params.showFiles = showFiles;
|
||||
params.matching = matching;
|
||||
params.wordsThreshold = wordsThreshold;
|
||||
params.matchingMaxComparisons = matchingMaxComparisons;
|
||||
} else {
|
||||
params.outputFormat = params.outputFormat || outputFormat;
|
||||
params.showFiles = String(params.showFiles) !== "false" || (params.showFiles === null && showFiles);
|
||||
params.matching = params.matching || matching;
|
||||
params.wordsThreshold = params.wordsThreshold || wordsThreshold;
|
||||
params.matchingMaxComparisons = params.matchingMaxComparisons || matchingMaxComparisons;
|
||||
|
||||
elements.outputFormat.value = params.outputFormat;
|
||||
elements.showFiles.setAttribute("checked", params.showFiles);
|
||||
elements.matching.value = params.matching;
|
||||
elements.wordsThreshold.value = params.wordsThreshold;
|
||||
elements.matchingMaxComparisons.value = params.matchingMaxComparisons;
|
||||
}
|
||||
|
||||
params.synchronisedScroll = params.synchronisedScroll || true;
|
||||
|
||||
const diff2htmlUi = new Diff2HtmlUI(data, elements.root);
|
||||
|
||||
if (outputFormat === "side-by-side") {
|
||||
elements.container.css({ width: "100%" });
|
||||
} else {
|
||||
elements.container.css({ width: "" });
|
||||
}
|
||||
|
||||
diff2htmlUi.draw();
|
||||
diff2htmlUi.fileListCloseable(params.fileListCloseable || false);
|
||||
if (params.highlight === undefined || params.highlight) {
|
||||
diff2htmlUi.highlightCode();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function smartDraw(urlOpt, urlElem, forced) {
|
||||
const url = urlOpt || urlElem.val();
|
||||
const req = prepareUrl(url);
|
||||
draw(req, forced);
|
||||
}
|
||||
|
||||
function bind(urlElem) {
|
||||
$("#url-btn").click(e => {
|
||||
e.preventDefault();
|
||||
const url = urlElem.val();
|
||||
smartDraw(url, urlElem);
|
||||
});
|
||||
|
||||
urlElem.on("paste", e => {
|
||||
const url = e.originalEvent.clipboardData.getData("Text");
|
||||
smartDraw(url, urlElem);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Improves browser compatibility
|
||||
require("whatwg-fetch");
|
||||
|
||||
const searchParam = "diff";
|
||||
|
||||
const $container = $(".container");
|
||||
const $url = $("#url");
|
||||
const $outputFormat = $("#diff-url-options-output-format");
|
||||
const $showFiles = $("#diff-url-options-show-files");
|
||||
const $matching = $("#diff-url-options-matching");
|
||||
const $wordsThreshold = $("#diff-url-options-match-words-threshold");
|
||||
const $matchingMaxComparisons = $("#diff-url-options-matching-max-comparisons");
|
||||
const elements = {
|
||||
root: document.getElementById("url-diff-container"),
|
||||
container: document.getElementsByClassName("container"),
|
||||
url: document.getElementById("url"),
|
||||
outputFormat: document.getElementById("diff-url-options-output-format"),
|
||||
showFiles: document.getElementById("diff-url-options-show-files"),
|
||||
matching: document.getElementById("diff-url-options-matching"),
|
||||
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold"),
|
||||
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons")
|
||||
};
|
||||
|
||||
if (window.location.search) {
|
||||
const url = getUrlFromSearch(window.location.search);
|
||||
$url.val(url);
|
||||
smartDraw(url);
|
||||
elements.url.val(url);
|
||||
smartDraw(url, elements.url);
|
||||
}
|
||||
|
||||
bind();
|
||||
|
||||
$outputFormat
|
||||
.add($showFiles)
|
||||
.add($matching)
|
||||
.add($wordsThreshold)
|
||||
.add($matchingMaxComparisons)
|
||||
.change(function(e) {
|
||||
console.log("");
|
||||
console.log(e);
|
||||
console.log("");
|
||||
smartDraw(null, true);
|
||||
});
|
||||
|
||||
function getUrlFromSearch(search) {
|
||||
try {
|
||||
return search
|
||||
.split("?")[1]
|
||||
.split(searchParam + "=")[1]
|
||||
.split("&")[0];
|
||||
} catch (_ignore) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getParamsFromSearch(search) {
|
||||
const map = {};
|
||||
try {
|
||||
search
|
||||
.split("?")[1]
|
||||
.split("&")
|
||||
.map(function(e) {
|
||||
const values = e.split("=");
|
||||
map[values[0]] = values[1];
|
||||
});
|
||||
} catch (_ignore) {}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
function bind() {
|
||||
$("#url-btn").click(function(e) {
|
||||
e.preventDefault();
|
||||
const url = $url.val();
|
||||
smartDraw(url);
|
||||
});
|
||||
|
||||
$url.on("paste", function(e) {
|
||||
const url = e.originalEvent.clipboardData.getData("Text");
|
||||
smartDraw(url);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareUrl(url) {
|
||||
let fetchUrl;
|
||||
const headers = new Headers();
|
||||
|
||||
const githubCommitUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const githubPrUrl = /^https?:\/\/(?:www\.)?github\.com\/(.*?)\/(.*?)\/pull\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const gitlabCommitUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/commit\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
const gitlabPrUrl = /^https?:\/\/(?:www\.)?gitlab\.com\/(.*?)\/(.*?)\/merge_requests\/(.*?)(?:\.diff)?(?:\.patch)?(?:\/.*)?$/;
|
||||
|
||||
const bitbucketCommitUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/commits\/(.*?)(?:\/raw)?(?:\/.*)?$/;
|
||||
const bitbucketPrUrl = /^https?:\/\/(?:www\.)?bitbucket\.org\/(.*?)\/(.*?)\/pull-requests\/(.*?)(?:\/.*)?$/;
|
||||
|
||||
function gitLabUrlGen(userName, projectName, type, value) {
|
||||
return (
|
||||
"https://crossorigin.me/https://gitlab.com/" + userName + "/" + projectName + "/" + type + "/" + value + ".diff"
|
||||
);
|
||||
}
|
||||
|
||||
function gitHubUrlGen(userName, projectName, type, value) {
|
||||
headers.append("Accept", "application/vnd.github.v3.diff");
|
||||
return "https://api.github.com/repos/" + userName + "/" + projectName + "/" + type + "/" + value;
|
||||
}
|
||||
|
||||
function bitbucketUrlGen(userName, projectName, type, value) {
|
||||
const baseUrl = "https://bitbucket.org/api/2.0/repositories/";
|
||||
if (type === "pullrequests") {
|
||||
return baseUrl + userName + "/" + projectName + "/pullrequests/" + value + "/diff";
|
||||
}
|
||||
return baseUrl + userName + "/" + projectName + "/diff/" + value;
|
||||
}
|
||||
|
||||
let values;
|
||||
if ((values = githubCommitUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "commits", values[3]);
|
||||
} else if ((values = githubPrUrl.exec(url))) {
|
||||
fetchUrl = gitHubUrlGen(values[1], values[2], "pulls", values[3]);
|
||||
} else if ((values = gitlabCommitUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = gitlabPrUrl.exec(url))) {
|
||||
fetchUrl = gitLabUrlGen(values[1], values[2], "merge_requests", values[3]);
|
||||
} else if ((values = bitbucketCommitUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "commit", values[3]);
|
||||
} else if ((values = bitbucketPrUrl.exec(url))) {
|
||||
fetchUrl = bitbucketUrlGen(values[1], values[2], "pullrequests", values[3]);
|
||||
} else {
|
||||
console.info("Could not parse url, using the provided url.");
|
||||
fetchUrl = "https://crossorigin.me/" + url;
|
||||
}
|
||||
|
||||
return {
|
||||
originalUrl: url,
|
||||
url: fetchUrl,
|
||||
headers: headers
|
||||
};
|
||||
}
|
||||
|
||||
function smartDraw(urlOpt, forced) {
|
||||
const url = urlOpt || $url.val();
|
||||
const req = prepareUrl(url);
|
||||
draw(req, forced);
|
||||
}
|
||||
|
||||
function draw(req, forced) {
|
||||
if (!validateUrl(req.url)) {
|
||||
console.error("Invalid url provided!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (validateUrl(req.originalUrl)) updateUrl(req.originalUrl);
|
||||
|
||||
const outputFormat = $outputFormat.val();
|
||||
const showFiles = $showFiles.is(":checked");
|
||||
const matching = $matching.val();
|
||||
const wordsThreshold = $wordsThreshold.val();
|
||||
const matchingMaxComparisons = $matchingMaxComparisons.val();
|
||||
|
||||
fetch(req.url, {
|
||||
method: "GET",
|
||||
headers: req.headers,
|
||||
mode: "cors",
|
||||
cache: "default"
|
||||
})
|
||||
.then(function(res) {
|
||||
return res.text();
|
||||
})
|
||||
.then(function(data) {
|
||||
const container = "#url-diff-container";
|
||||
const diff2htmlUi = new Diff2HtmlUI({ diff: data });
|
||||
|
||||
if (outputFormat === "side-by-side") {
|
||||
$container.css({ width: "100%" });
|
||||
} else {
|
||||
$container.css({ width: "" });
|
||||
}
|
||||
|
||||
const params = getParamsFromSearch(window.location.search);
|
||||
delete params[searchParam];
|
||||
|
||||
if (forced) {
|
||||
params.outputFormat = outputFormat;
|
||||
params.showFiles = showFiles;
|
||||
params.matching = matching;
|
||||
params.wordsThreshold = wordsThreshold;
|
||||
params.matchingMaxComparisons = matchingMaxComparisons;
|
||||
} else {
|
||||
params.outputFormat = params.outputFormat || outputFormat;
|
||||
params.showFiles = String(params.showFiles) !== "false" || (params.showFiles === null && showFiles);
|
||||
params.matching = params.matching || matching;
|
||||
params.wordsThreshold = params.wordsThreshold || wordsThreshold;
|
||||
params.matchingMaxComparisons = params.matchingMaxComparisons || matchingMaxComparisons;
|
||||
|
||||
$outputFormat.val(params.outputFormat);
|
||||
$showFiles.prop("checked", params.showFiles);
|
||||
$matching.val(params.matching);
|
||||
$wordsThreshold.val(params.wordsThreshold);
|
||||
$matchingMaxComparisons.val(params.matchingMaxComparisons);
|
||||
}
|
||||
|
||||
params.synchronisedScroll = params.synchronisedScroll || true;
|
||||
|
||||
diff2htmlUi.draw(container, params);
|
||||
diff2htmlUi.fileListCloseable(container, params.fileListCloseable || false);
|
||||
if (params.highlight === undefined || params.highlight) {
|
||||
diff2htmlUi.highlightCode(container);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateUrl(url) {
|
||||
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||
url
|
||||
);
|
||||
}
|
||||
|
||||
function updateUrl(url) {
|
||||
const params = getParamsFromSearch(window.location.search);
|
||||
|
||||
if (params[searchParam] === url) return;
|
||||
|
||||
params[searchParam] = url;
|
||||
|
||||
const paramString = Object.keys(params)
|
||||
.map(function(k) {
|
||||
return k + "=" + params[k];
|
||||
})
|
||||
.join("&");
|
||||
|
||||
window.location = "demo.html?" + paramString;
|
||||
}
|
||||
elements.outputFormat
|
||||
.add(elements.showFiles)
|
||||
.add(elements.matching)
|
||||
.add(elements.wordsThreshold)
|
||||
.add(elements.matchingMaxComparisons)
|
||||
.change(() => smartDraw(null, elements.url, true));
|
||||
});
|
||||
|
||||
/* eslint-enable @typescript-eslint/explicit-function-return-type */
|
||||
|
|
|
|||
227
yarn.lock
227
yarn.lock
|
|
@ -10,17 +10,17 @@
|
|||
"@babel/highlight" "^7.0.0"
|
||||
|
||||
"@babel/core@^7.1.0":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91"
|
||||
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==
|
||||
version "7.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
|
||||
integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.5.5"
|
||||
"@babel/generator" "^7.6.2"
|
||||
"@babel/generator" "^7.6.4"
|
||||
"@babel/helpers" "^7.6.2"
|
||||
"@babel/parser" "^7.6.2"
|
||||
"@babel/parser" "^7.6.4"
|
||||
"@babel/template" "^7.6.0"
|
||||
"@babel/traverse" "^7.6.2"
|
||||
"@babel/types" "^7.6.0"
|
||||
"@babel/traverse" "^7.6.3"
|
||||
"@babel/types" "^7.6.3"
|
||||
convert-source-map "^1.1.0"
|
||||
debug "^4.1.0"
|
||||
json5 "^2.1.0"
|
||||
|
|
@ -29,12 +29,12 @@
|
|||
semver "^5.4.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.4.0", "@babel/generator@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03"
|
||||
integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==
|
||||
"@babel/generator@^7.4.0", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4":
|
||||
version "7.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
|
||||
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
|
||||
dependencies:
|
||||
"@babel/types" "^7.6.0"
|
||||
"@babel/types" "^7.6.3"
|
||||
jsesc "^2.5.1"
|
||||
lodash "^4.17.13"
|
||||
source-map "^0.5.0"
|
||||
|
|
@ -85,10 +85,10 @@
|
|||
esutils "^2.0.2"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
|
||||
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4":
|
||||
version "7.6.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
|
||||
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
|
||||
|
||||
"@babel/plugin-syntax-object-rest-spread@^7.0.0":
|
||||
version "7.2.0"
|
||||
|
|
@ -106,25 +106,25 @@
|
|||
"@babel/parser" "^7.6.0"
|
||||
"@babel/types" "^7.6.0"
|
||||
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2":
|
||||
version "7.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c"
|
||||
integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==
|
||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3":
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
|
||||
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.5.5"
|
||||
"@babel/generator" "^7.6.2"
|
||||
"@babel/generator" "^7.6.3"
|
||||
"@babel/helper-function-name" "^7.1.0"
|
||||
"@babel/helper-split-export-declaration" "^7.4.4"
|
||||
"@babel/parser" "^7.6.2"
|
||||
"@babel/types" "^7.6.0"
|
||||
"@babel/parser" "^7.6.3"
|
||||
"@babel/types" "^7.6.3"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.13"
|
||||
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0":
|
||||
version "7.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648"
|
||||
integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0", "@babel/types@^7.6.3":
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
|
||||
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.17.13"
|
||||
|
|
@ -332,6 +332,11 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/diff@4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c"
|
||||
integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
|
|
@ -351,10 +356,10 @@
|
|||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/hogan.js@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.0.tgz#bf26560f39a38224ab6d0491b06f72c8fbe0953d"
|
||||
integrity sha512-djkvb/AN43c3lIGCojNQ1FBS9VqqKhcTns5RQnHw4xBT/csy0jAssAsOiJ8NfaaioZaeKYE7XkVRxE5NeSZcaA==
|
||||
"@types/highlight.js@9.12.3":
|
||||
version "9.12.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
|
||||
integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.1"
|
||||
|
|
@ -398,19 +403,24 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/mkdirp@^0.5.2":
|
||||
"@types/mkdirp@0.5.2":
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
|
||||
integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@^12.7.2":
|
||||
version "12.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
|
||||
integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==
|
||||
"@types/node@*":
|
||||
version "12.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc"
|
||||
integrity sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==
|
||||
|
||||
"@types/nopt@^3.0.29":
|
||||
"@types/node@12.7.2":
|
||||
version "12.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44"
|
||||
integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==
|
||||
|
||||
"@types/nopt@3.0.29":
|
||||
version "3.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce"
|
||||
integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4=
|
||||
|
|
@ -726,18 +736,18 @@ atob@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
autoprefixer@^9.6.0:
|
||||
version "9.6.4"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.4.tgz#e6453be47af316b2923eaeaed87860f52ad4b7eb"
|
||||
integrity sha512-Koz2cJU9dKOxG8P1f8uVaBntOv9lP4yz9ffWvWaicv9gHBPhpQB22nGijwd8gqW9CNT+UdkbQOQNLVI8jN1ZfQ==
|
||||
autoprefixer@9.6.0:
|
||||
version "9.6.0"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
|
||||
integrity sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==
|
||||
dependencies:
|
||||
browserslist "^4.7.0"
|
||||
caniuse-lite "^1.0.30000998"
|
||||
browserslist "^4.6.1"
|
||||
caniuse-lite "^1.0.30000971"
|
||||
chalk "^2.4.2"
|
||||
normalize-range "^0.1.2"
|
||||
num2fraction "^1.2.2"
|
||||
postcss "^7.0.18"
|
||||
postcss-value-parser "^4.0.2"
|
||||
postcss "^7.0.16"
|
||||
postcss-value-parser "^3.3.1"
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
|
|
@ -949,7 +959,7 @@ browserify-zlib@~0.2.0:
|
|||
dependencies:
|
||||
pako "~1.0.5"
|
||||
|
||||
browserify@^16.5.0:
|
||||
browserify@16.5.0:
|
||||
version "16.5.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881"
|
||||
integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==
|
||||
|
|
@ -1003,7 +1013,7 @@ browserify@^16.5.0:
|
|||
vm-browserify "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
browserslist@^4.7.0:
|
||||
browserslist@^4.6.1:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
|
||||
integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==
|
||||
|
|
@ -1108,7 +1118,7 @@ camelcase@^5.0.0, camelcase@^5.3.1:
|
|||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000998:
|
||||
caniuse-lite@^1.0.30000971, caniuse-lite@^1.0.30000989:
|
||||
version "1.0.30000999"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
|
||||
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
|
||||
|
|
@ -1186,7 +1196,7 @@ class-utils@^0.3.5:
|
|||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
clean-css-cli@^4.3.0:
|
||||
clean-css-cli@4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-css-cli/-/clean-css-cli-4.3.0.tgz#8502aa86d1879e5b111af51b3c2abb799e0684ce"
|
||||
integrity sha512-8GHZfr+mG3zB/Lgqrr27qHBFsPSn0fyEI3f2rIZpxPxUbn2J6A8xyyeBRVTW8duDuXigN0s80vsXiXJOEFIO5Q==
|
||||
|
|
@ -1237,7 +1247,7 @@ co@^4.6.0:
|
|||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
|
||||
|
||||
codacy-coverage@^3.4.0:
|
||||
codacy-coverage@3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-3.4.0.tgz#196af70844c4e4179718f7a7f9d96b921b4b3a67"
|
||||
integrity sha512-A0ats3/gZtOw76muu++HZ6QrInztWjjLefkLJmmBpjPfyn6nNwNLoApmGmj3F3dfgl2+o6u5GwPnUBkKdfKXTQ==
|
||||
|
|
@ -1299,10 +1309,15 @@ commander@2.15.1:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
|
||||
|
||||
commander@2.x, commander@^2.20.0, commander@^2.x, commander@~2.20.0:
|
||||
version "2.20.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
|
||||
integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==
|
||||
commander@2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
|
||||
|
||||
commander@2.x, commander@^2.20.0, commander@^2.x:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
version "1.3.0"
|
||||
|
|
@ -1619,7 +1634,7 @@ diff@3.5.0:
|
|||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
diff@^4.0.1:
|
||||
diff@4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
||||
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
|
||||
|
|
@ -1683,9 +1698,9 @@ ecc-jsbn@~0.1.1:
|
|||
safer-buffer "^2.1.0"
|
||||
|
||||
electron-to-chromium@^1.3.247:
|
||||
version "1.3.275"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.275.tgz#19a38436e34216f51820fa2f4326d5ce141fa36f"
|
||||
integrity sha512-/YWtW/VapMnuYA1lNOaa1F4GhR1LBf+CUTp60lzDPEEh0XOzyOAyULyYZVF9vziZ3qSbTqCwmKwsyRXp66STbw==
|
||||
version "1.3.281"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.281.tgz#2dbeb9f0bdffddb1662f9ca00d26c49d31dc0f7e"
|
||||
integrity sha512-oxXKngPjTWRmXFy4vV9FeAkPl7wU4xMejfOY+HXjGrj4T0z9l96loWWVDLJEtbT/aPKOWKrSz6xoYxd+YJ/gJA==
|
||||
|
||||
elliptic@^6.0.0:
|
||||
version "6.5.1"
|
||||
|
|
@ -1845,12 +1860,12 @@ eslint-plugin-prettier@3.1.0:
|
|||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
|
||||
eslint-plugin-promise@^4.2.1:
|
||||
eslint-plugin-promise@4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||
|
||||
eslint-plugin-standard@^4.0.1:
|
||||
eslint-plugin-standard@4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
|
||||
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
|
||||
|
|
@ -2105,7 +2120,7 @@ fast-glob@^2.2.6:
|
|||
merge2 "^1.2.3"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
fast-html-parser@^1.0.1:
|
||||
fast-html-parser@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-html-parser/-/fast-html-parser-1.0.1.tgz#4ecc9683b8bb79afe11a50807b7853e79256cea2"
|
||||
integrity sha1-TsyWg7i7ea/hGlCAe3hT55JWzqI=
|
||||
|
|
@ -2379,9 +2394,9 @@ growly@^1.3.0:
|
|||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
||||
|
||||
handlebars@^4.1.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.2.tgz#8810a9821a9d6d52cb2f57d326d6ce7c3dfe741d"
|
||||
integrity sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.3.tgz#180bae52c1d0e9ec0c15d7e82a4362d662762f6e"
|
||||
integrity sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==
|
||||
dependencies:
|
||||
neo-async "^2.6.0"
|
||||
optimist "^0.6.1"
|
||||
|
|
@ -2476,6 +2491,11 @@ he@1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
|
||||
|
||||
highlight.js@9.15.10:
|
||||
version "9.15.10"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
|
||||
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
|
|
@ -2495,7 +2515,7 @@ hoek@6.x.x:
|
|||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
|
||||
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
|
||||
|
||||
hogan.js@^3.0.2:
|
||||
hogan.js@3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
||||
integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
|
||||
|
|
@ -2504,9 +2524,9 @@ hogan.js@^3.0.2:
|
|||
nopt "1.0.10"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.4.tgz#44119abaf4bc64692a16ace34700fed9c03e2546"
|
||||
integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==
|
||||
version "2.8.5"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
|
||||
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
|
||||
|
||||
html-encoding-sniffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
|
@ -2547,9 +2567,9 @@ ieee754@^1.1.4:
|
|||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||
|
||||
ignore-walk@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b"
|
||||
integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw==
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
|
||||
integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
|
|
@ -3548,6 +3568,11 @@ locate-path@^3.0.0:
|
|||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash.memoize@4.x:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.memoize@~3.0.3:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
|
||||
|
|
@ -3654,11 +3679,6 @@ merge2@^1.2.3:
|
|||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
|
||||
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
|
||||
|
||||
merge@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
|
||||
integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
|
||||
|
||||
micromatch@^3.1.10, micromatch@^3.1.4:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||
|
|
@ -3912,9 +3932,9 @@ node-pre-gyp@^0.12.0:
|
|||
tar "^4"
|
||||
|
||||
node-releases@^1.1.29:
|
||||
version "1.1.34"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.34.tgz#ced4655ee1ba9c3a2c5dcbac385e19434155fd40"
|
||||
integrity sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA==
|
||||
version "1.1.35"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.35.tgz#32a74a3cd497aa77f23d509f483475fd160e4c48"
|
||||
integrity sha512-JGcM/wndCN/2elJlU0IGdVEJQQnJwsLbgPCFd2pY7V0mxf17bZ0Gb/lgOtL29ZQhvEX5shnVhxQyZz3ex94N8w==
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
|
|
@ -3925,7 +3945,7 @@ nopt@1.0.10:
|
|||
dependencies:
|
||||
abbrev "1"
|
||||
|
||||
nopt@^4.0.1:
|
||||
nopt@4.0.1, nopt@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
||||
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
|
||||
|
|
@ -3966,9 +3986,9 @@ npm-bundled@^1.0.1:
|
|||
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
|
||||
|
||||
npm-packlist@^1.1.6:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
|
||||
integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
|
||||
version "1.4.6"
|
||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4"
|
||||
integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==
|
||||
dependencies:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
|
@ -4364,7 +4384,7 @@ posix-character-classes@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
||||
|
||||
postcss-cli@^6.1.3:
|
||||
postcss-cli@6.1.3:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-6.1.3.tgz#a9eec3e9cde4aaa90170546baf706f8af6f8ecec"
|
||||
integrity sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw==
|
||||
|
|
@ -4400,12 +4420,12 @@ postcss-reporter@^6.0.0:
|
|||
log-symbols "^2.2.0"
|
||||
postcss "^7.0.7"
|
||||
|
||||
postcss-value-parser@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
|
||||
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
|
||||
postcss-value-parser@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
|
||||
|
||||
postcss@^7.0.0, postcss@^7.0.18, postcss@^7.0.7:
|
||||
postcss@^7.0.0, postcss@^7.0.16, postcss@^7.0.7:
|
||||
version "7.0.18"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
|
||||
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
|
||||
|
|
@ -5299,7 +5319,7 @@ tar@^4:
|
|||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.3"
|
||||
|
||||
terser@^4.3.8:
|
||||
terser@4.3.8:
|
||||
version "4.3.8"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136"
|
||||
integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==
|
||||
|
|
@ -5420,15 +5440,16 @@ tr46@^1.0.1:
|
|||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
ts-jest@24.0.2:
|
||||
version "24.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.2.tgz#8dde6cece97c31c03e80e474c749753ffd27194d"
|
||||
integrity sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==
|
||||
ts-jest@24.1.0:
|
||||
version "24.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734"
|
||||
integrity sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==
|
||||
dependencies:
|
||||
bs-logger "0.x"
|
||||
buffer-from "1.x"
|
||||
fast-json-stable-stringify "2.x"
|
||||
json5 "2.x"
|
||||
lodash.memoize "4.x"
|
||||
make-error "1.x"
|
||||
mkdirp "0.x"
|
||||
resolve "1.x"
|
||||
|
|
@ -5476,17 +5497,17 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^3.6.3:
|
||||
version "3.6.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
|
||||
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
|
||||
typescript@3.6.4:
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
|
||||
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
|
||||
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.1.tgz#ae7688c50e1bdcf2f70a0e162410003cf9798311"
|
||||
integrity sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==
|
||||
dependencies:
|
||||
commander "~2.20.0"
|
||||
commander "2.20.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
umd@^3.0.0:
|
||||
|
|
@ -5643,7 +5664,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
|
|||
dependencies:
|
||||
iconv-lite "0.4.24"
|
||||
|
||||
whatwg-fetch@^3.0.0:
|
||||
whatwg-fetch@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||
|
|
|
|||
Loading…
Reference in a new issue