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/**
|
build/**
|
||||||
docs/**
|
docs/**
|
||||||
node_modules/**
|
node_modules/**
|
||||||
src/diff2html-templates.js
|
src/diff2html-templates.*
|
||||||
|
typings/**
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -32,4 +32,4 @@ bower_components/
|
||||||
/docs/
|
/docs/
|
||||||
/dist/
|
/dist/
|
||||||
/build/
|
/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`
|
- `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`
|
- `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`
|
- `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
|
- `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`
|
- `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)
|
> 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": "jest --collectCoverage",
|
||||||
"coverage-html": "yarn run coverage && open ./coverage/index.html",
|
"coverage-html": "yarn run coverage && open ./coverage/index.html",
|
||||||
"codacy": "cat ./coverage/lcov.info | codacy-coverage",
|
"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-scripts": "tsc -p tsconfig.scripts.json",
|
||||||
"build-css": "./scripts/build-css.sh",
|
"build-css": "./scripts/build-css.sh",
|
||||||
"build-templates": "./scripts/build-templates.sh",
|
"build-templates": "./scripts/build-templates.sh",
|
||||||
|
|
@ -57,23 +57,22 @@
|
||||||
"fs": false
|
"fs": false
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff": "^4.0.1",
|
"diff": "4.0.1",
|
||||||
"hogan.js": "^3.0.2",
|
"hogan.js": "3.0.2"
|
||||||
"merge": "^1.2.1",
|
|
||||||
"whatwg-fetch": "^3.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/hogan.js": "^3.0.0",
|
"@types/diff": "4.0.2",
|
||||||
|
"@types/highlight.js": "9.12.3",
|
||||||
"@types/jest": "24.0.18",
|
"@types/jest": "24.0.18",
|
||||||
"@types/mkdirp": "^0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/node": "^12.7.2",
|
"@types/node": "12.7.2",
|
||||||
"@types/nopt": "^3.0.29",
|
"@types/nopt": "3.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "2.0.0",
|
"@typescript-eslint/eslint-plugin": "2.0.0",
|
||||||
"@typescript-eslint/parser": "2.0.0",
|
"@typescript-eslint/parser": "2.0.0",
|
||||||
"autoprefixer": "^9.6.0",
|
"autoprefixer": "9.6.0",
|
||||||
"browserify": "^16.5.0",
|
"browserify": "16.5.0",
|
||||||
"clean-css-cli": "^4.3.0",
|
"clean-css-cli": "4.3.0",
|
||||||
"codacy-coverage": "^3.4.0",
|
"codacy-coverage": "3.4.0",
|
||||||
"eslint": "6.2.2",
|
"eslint": "6.2.2",
|
||||||
"eslint-config-prettier": "6.1.0",
|
"eslint-config-prettier": "6.1.0",
|
||||||
"eslint-config-standard": "14.0.1",
|
"eslint-config-standard": "14.0.1",
|
||||||
|
|
@ -81,17 +80,19 @@
|
||||||
"eslint-plugin-jest": "22.15.2",
|
"eslint-plugin-jest": "22.15.2",
|
||||||
"eslint-plugin-node": "9.1.0",
|
"eslint-plugin-node": "9.1.0",
|
||||||
"eslint-plugin-prettier": "3.1.0",
|
"eslint-plugin-prettier": "3.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "4.0.1",
|
||||||
"fast-html-parser": "^1.0.1",
|
"fast-html-parser": "1.0.1",
|
||||||
|
"highlight.js": "9.15.10",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"nopt": "^4.0.1",
|
"nopt": "4.0.1",
|
||||||
"postcss-cli": "^6.1.3",
|
"postcss-cli": "6.1.3",
|
||||||
"prettier": "1.18.2",
|
"prettier": "1.18.2",
|
||||||
"terser": "^4.3.8",
|
"terser": "4.3.8",
|
||||||
"ts-jest": "24.0.2",
|
"ts-jest": "24.1.0",
|
||||||
"typescript": "^3.6.3"
|
"typescript": "3.6.4",
|
||||||
|
"whatwg-fetch": "3.0.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"lodash": "4.17.15"
|
"lodash": "4.17.15"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@ set -e
|
||||||
SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )"
|
SCRIPT_DIRECTORY="$( cd "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \
|
node ${SCRIPT_DIRECTORY}/../build/scripts/hulk.js \
|
||||||
--wrapper node \
|
--wrapper ts \
|
||||||
--variable 'browserTemplates' \
|
--variable 'defaultTemplates' \
|
||||||
${SCRIPT_DIRECTORY}/../src/templates/*.mustache > ${SCRIPT_DIRECTORY}/../src/diff2html-templates.js
|
${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 fs from "fs";
|
||||||
|
|
||||||
import * as hogan from "hogan.js";
|
import * as hogan from "hogan.js";
|
||||||
import * as nopt from "nopt";
|
import nopt from "nopt";
|
||||||
import * as mkderp from "mkdirp";
|
import * as mkderp from "mkdirp";
|
||||||
|
|
||||||
const options = nopt(
|
const options = nopt(
|
||||||
|
|
@ -52,10 +52,9 @@ function cyan(text: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractFiles(files: string[]): string[] {
|
function extractFiles(files: string[]): string[] {
|
||||||
const usage = `
|
const usage = `${cyan(
|
||||||
${cyan(
|
"USAGE:"
|
||||||
"USAGE:"
|
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
||||||
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
|
||||||
|
|
||||||
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd)
|
${cyan("OPTIONS:")} [-w, --wrapper] :: wraps the template (i.e. amd)
|
||||||
[-o, --outputdir] :: outputs the templates as individual files to a directory
|
[-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
|
// If we have a template per file the export will expose the template directly
|
||||||
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
|
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
|
||||||
|
|
||||||
|
case "ts":
|
||||||
|
return `// @ts-ignore\n${objectStmt}`;
|
||||||
default:
|
default:
|
||||||
return objectStmt;
|
return objectStmt;
|
||||||
}
|
}
|
||||||
|
|
@ -141,19 +142,18 @@ function prepareOutput(content: string): string {
|
||||||
case "amd":
|
case "amd":
|
||||||
return content;
|
return content;
|
||||||
case "node":
|
case "node":
|
||||||
return (
|
return `(function() {
|
||||||
"(function() {\n" +
|
if (!!!global.${variableName}) global.${variableName} = {};
|
||||||
"if (!!!global." +
|
var Hogan = require("hogan.js");
|
||||||
variableName +
|
${content}
|
||||||
") global." +
|
${!options.outputdir ? `module.exports = global.${variableName};\n` : ""})();`;
|
||||||
variableName +
|
|
||||||
" = {};\n" +
|
case "ts":
|
||||||
'var Hogan = require("hogan.js");' +
|
return `import * as Hogan from "hogan.js";
|
||||||
content +
|
type CompiledTemplates = { [name: string]: Hogan.Template };
|
||||||
"\n" +
|
export const ${variableName}: CompiledTemplates = {};
|
||||||
(!options.outputdir ? "module.exports = global." + variableName + ";\n" : "") +
|
${content}`;
|
||||||
"})();"
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
|
return "if (!!!" + variableName + ") var " + variableName + " = {};\n" + content;
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +181,9 @@ const templates = extractFiles(options.argv.remain)
|
||||||
|
|
||||||
if (!options.outputdir) return cleanFileContents;
|
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");
|
.filter(templateContents => typeof templateContents !== "undefined");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
const DiffParser = require("../diff-parser.js").DiffParser;
|
import { parse } from "../diff-parser";
|
||||||
|
|
||||||
function checkDiffSample(diff) {
|
function checkDiffSample(diff: string): void {
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample");
|
||||||
expect("sample").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("DiffParser", function() {
|
describe("DiffParser", () => {
|
||||||
describe("generateDiffJson", function() {
|
describe("generateDiffJson", () => {
|
||||||
it("should parse unix with \n diff", function() {
|
it("should parse unix with \n diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\n" +
|
"diff --git a/sample b/sample\n" +
|
||||||
"index 0000001..0ddf2ba\n" +
|
"index 0000001..0ddf2ba\n" +
|
||||||
|
|
@ -25,7 +25,7 @@ describe("DiffParser", function() {
|
||||||
checkDiffSample(diff);
|
checkDiffSample(diff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse windows with \r\n diff", function() {
|
it("should parse windows with \r\n diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\r\n" +
|
"diff --git a/sample b/sample\r\n" +
|
||||||
"index 0000001..0ddf2ba\r\n" +
|
"index 0000001..0ddf2ba\r\n" +
|
||||||
|
|
@ -37,7 +37,7 @@ describe("DiffParser", function() {
|
||||||
checkDiffSample(diff);
|
checkDiffSample(diff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse old os x with \r diff", function() {
|
it("should parse old os x with \r diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\r" +
|
"diff --git a/sample b/sample\r" +
|
||||||
"index 0000001..0ddf2ba\r" +
|
"index 0000001..0ddf2ba\r" +
|
||||||
|
|
@ -49,7 +49,7 @@ describe("DiffParser", function() {
|
||||||
checkDiffSample(diff);
|
checkDiffSample(diff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse mixed eols diff", function() {
|
it("should parse mixed eols diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\n" +
|
"diff --git a/sample b/sample\n" +
|
||||||
"index 0000001..0ddf2ba\r\n" +
|
"index 0000001..0ddf2ba\r\n" +
|
||||||
|
|
@ -61,7 +61,7 @@ describe("DiffParser", function() {
|
||||||
checkDiffSample(diff);
|
checkDiffSample(diff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with special characters", function() {
|
it("should parse diff with special characters", () => {
|
||||||
const diff =
|
const diff =
|
||||||
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
|
'diff --git "a/bla with \ttab.scala" "b/bla with \ttab.scala"\n' +
|
||||||
"index 4c679d7..e9bd385 100644\n" +
|
"index 4c679d7..e9bd385 100644\n" +
|
||||||
|
|
@ -72,17 +72,17 @@ describe("DiffParser", function() {
|
||||||
"+cenas com ananas\n" +
|
"+cenas com ananas\n" +
|
||||||
"+bananas";
|
"+bananas";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("bla with \ttab.scala").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||||
expect("bla with \ttab.scala").toEqual(file1.newName);
|
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with prefix", function() {
|
it("should parse diff with prefix", () => {
|
||||||
const diff =
|
const diff =
|
||||||
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
|
'diff --git "\tbla with \ttab.scala" "\tbla with \ttab.scala"\n' +
|
||||||
"index 4c679d7..e9bd385 100644\n" +
|
"index 4c679d7..e9bd385 100644\n" +
|
||||||
|
|
@ -93,17 +93,17 @@ describe("DiffParser", function() {
|
||||||
"+cenas com ananas\n" +
|
"+cenas com ananas\n" +
|
||||||
"+bananas";
|
"+bananas";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("bla with \ttab.scala").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("bla with \ttab.scala");
|
||||||
expect("bla with \ttab.scala").toEqual(file1.newName);
|
expect(file1.newName).toEqual("bla with \ttab.scala");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with deleted file", function() {
|
it("should parse diff with deleted file", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
|
"diff --git a/src/var/strundefined.js b/src/var/strundefined.js\n" +
|
||||||
"deleted file mode 100644\n" +
|
"deleted file mode 100644\n" +
|
||||||
|
|
@ -111,26 +111,26 @@ describe("DiffParser", function() {
|
||||||
"--- a/src/var/strundefined.js\n" +
|
"--- a/src/var/strundefined.js\n" +
|
||||||
"+++ /dev/null\n" +
|
"+++ /dev/null\n" +
|
||||||
"@@ -1,3 +0,0 @@\n" +
|
"@@ -1,3 +0,0 @@\n" +
|
||||||
"-define(function() {\n" +
|
"-define(() => {\n" +
|
||||||
"- return typeof undefined;\n" +
|
"- return typeof undefined;\n" +
|
||||||
"-});\n";
|
"-});\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(false).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(false);
|
||||||
expect(0).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(0);
|
||||||
expect(3).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(3);
|
||||||
expect("src/var/strundefined.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("src/var/strundefined.js");
|
||||||
expect("/dev/null").toEqual(file1.newName);
|
expect(file1.newName).toEqual("/dev/null");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(true).toEqual(file1.isDeleted);
|
expect(file1.isDeleted).toEqual(true);
|
||||||
expect("04e16b0").toEqual(file1.checksumBefore);
|
expect(file1.checksumBefore).toEqual("04e16b0");
|
||||||
expect("0000000").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("0000000");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with new file", function() {
|
it("should parse diff with new file", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/test.js b/test.js\n" +
|
"diff --git a/test.js b/test.js\n" +
|
||||||
"new file mode 100644\n" +
|
"new file mode 100644\n" +
|
||||||
|
|
@ -144,23 +144,23 @@ describe("DiffParser", function() {
|
||||||
"+\n" +
|
"+\n" +
|
||||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(false).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(false);
|
||||||
expect(5).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(5);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("/dev/null").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("/dev/null");
|
||||||
expect("test.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("test.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(true).toEqual(file1.isNew);
|
expect(file1.isNew).toEqual(true);
|
||||||
expect("100644").toEqual(file1.newFileMode);
|
expect(file1.newFileMode).toEqual("100644");
|
||||||
expect("0000000").toEqual(file1.checksumBefore);
|
expect(file1.checksumBefore).toEqual("0000000");
|
||||||
expect("e1e22ec").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("e1e22ec");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with nested diff", function() {
|
it("should parse diff with nested diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/src/offset.js b/src/offset.js\n" +
|
"diff --git a/src/offset.js b/src/offset.js\n" +
|
||||||
"index cc6ffb4..fa51f18 100644\n" +
|
"index cc6ffb4..fa51f18 100644\n" +
|
||||||
|
|
@ -174,22 +174,22 @@ describe("DiffParser", function() {
|
||||||
"+\n" +
|
"+\n" +
|
||||||
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
"+console.log(parser.parsePatchDiffResult(text, patchLineList));\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(false).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(false);
|
||||||
expect(6).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(6);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("src/offset.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("src/offset.js");
|
||||||
expect("src/offset.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("src/offset.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(6).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(6);
|
||||||
expect("cc6ffb4").toEqual(file1.checksumBefore);
|
expect(file1.checksumBefore).toEqual("cc6ffb4");
|
||||||
expect("fa51f18").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("fa51f18");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with multiple blocks", function() {
|
it("should parse diff with multiple blocks", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
|
"diff --git a/src/attributes/classes.js b/src/attributes/classes.js\n" +
|
||||||
"index c617824..c8d1393 100644\n" +
|
"index c617824..c8d1393 100644\n" +
|
||||||
|
|
@ -217,23 +217,23 @@ describe("DiffParser", function() {
|
||||||
" // store className if set\n" +
|
" // store className if set\n" +
|
||||||
' dataPriv.set( this, "__className__", this.className );\n';
|
' dataPriv.set( this, "__className__", this.className );\n';
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(false).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(false);
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(3).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(3);
|
||||||
expect("src/attributes/classes.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("src/attributes/classes.js");
|
||||||
expect("src/attributes/classes.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("src/attributes/classes.js");
|
||||||
expect(2).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(2);
|
||||||
expect(11).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(11);
|
||||||
expect(8).toEqual(file1.blocks[1].lines.length);
|
expect(file1.blocks[1].lines.length).toEqual(8);
|
||||||
expect("c617824").toEqual(file1.checksumBefore);
|
expect(file1.checksumBefore).toEqual("c617824");
|
||||||
expect("c8d1393").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("c8d1393");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diff with multiple files", function() {
|
it("should parse diff with multiple files", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/src/core/init.js b/src/core/init.js\n" +
|
"diff --git a/src/core/init.js b/src/core/init.js\n" +
|
||||||
"index e49196a..50f310c 100644\n" +
|
"index e49196a..50f310c 100644\n" +
|
||||||
|
|
@ -260,33 +260,33 @@ describe("DiffParser", function() {
|
||||||
' "./var/hasOwn",\n' +
|
' "./var/hasOwn",\n' +
|
||||||
' "./var/slice",\n';
|
' "./var/slice",\n';
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(2).toEqual(result.length);
|
expect(result.length).toEqual(2);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(false).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(false);
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("src/core/init.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("src/core/init.js");
|
||||||
expect("src/core/init.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("src/core/init.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(8).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||||
expect("e49196a").toEqual(file1.checksumBefore);
|
expect(file1.checksumBefore).toEqual("e49196a");
|
||||||
expect("50f310c").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("50f310c");
|
||||||
|
|
||||||
const file2 = result[1];
|
const file2 = result[1];
|
||||||
expect(false).toEqual(file2.isCombined);
|
expect(file2.isCombined).toEqual(false);
|
||||||
expect(0).toEqual(file2.addedLines);
|
expect(file2.addedLines).toEqual(0);
|
||||||
expect(1).toEqual(file2.deletedLines);
|
expect(file2.deletedLines).toEqual(1);
|
||||||
expect("src/event.js").toEqual(file2.oldName);
|
expect(file2.oldName).toEqual("src/event.js");
|
||||||
expect("src/event.js").toEqual(file2.newName);
|
expect(file2.newName).toEqual("src/event.js");
|
||||||
expect(1).toEqual(file2.blocks.length);
|
expect(file2.blocks.length).toEqual(1);
|
||||||
expect(6).toEqual(file2.blocks[0].lines.length);
|
expect(file2.blocks[0].lines.length).toEqual(6);
|
||||||
expect("7336f4d").toEqual(file2.checksumBefore);
|
expect(file2.checksumBefore).toEqual("7336f4d");
|
||||||
expect("6183f70").toEqual(file2.checksumAfter);
|
expect(file2.checksumAfter).toEqual("6183f70");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse combined diff", function() {
|
it("should parse combined diff", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --combined describe.c\n" +
|
"diff --combined describe.c\n" +
|
||||||
"index fabadb8,cc95eb0..4866510\n" +
|
"index fabadb8,cc95eb0..4866510\n" +
|
||||||
|
|
@ -316,62 +316,62 @@ describe("DiffParser", function() {
|
||||||
" initialized = 1;\n" +
|
" initialized = 1;\n" +
|
||||||
" for_each_ref(get_name);\n";
|
" for_each_ref(get_name);\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(true).toEqual(file1.isCombined);
|
expect(file1.isCombined).toEqual(true);
|
||||||
expect(9).toEqual(file1.addedLines);
|
expect(9).toEqual(file1.addedLines);
|
||||||
expect(2).toEqual(file1.deletedLines);
|
expect(2).toEqual(file1.deletedLines);
|
||||||
expect("describe.c").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("describe.c");
|
||||||
expect("describe.c").toEqual(file1.newName);
|
expect(file1.newName).toEqual("describe.c");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(22).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(22);
|
||||||
expect(["4866510", "cc95eb0"].sort()).toEqual(file1.checksumBefore.sort());
|
expect(file1.checksumBefore).toEqual(["cc95eb0", "4866510"]);
|
||||||
expect("fabadb8").toEqual(file1.checksumAfter);
|
expect(file1.checksumAfter).toEqual("fabadb8");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diffs with copied files", function() {
|
it("should parse diffs with copied files", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/index.js b/more-index.js\n" +
|
"diff --git a/index.js b/more-index.js\n" +
|
||||||
"dissimilarity index 5%\n" +
|
"dissimilarity index 5%\n" +
|
||||||
"copy from index.js\n" +
|
"copy from index.js\n" +
|
||||||
"copy to more-index.js\n";
|
"copy to more-index.js\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(0).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(0);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("index.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("index.js");
|
||||||
expect("more-index.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("more-index.js");
|
||||||
expect(0).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(0);
|
||||||
expect(true).toEqual(file1.isCopy);
|
expect(file1.isCopy).toEqual(true);
|
||||||
expect("5").toEqual(file1.changedPercentage);
|
expect(file1.changedPercentage).toEqual(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diffs with moved files", function() {
|
it("should parse diffs with moved files", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/more-index.js b/other-index.js\n" +
|
"diff --git a/more-index.js b/other-index.js\n" +
|
||||||
"similarity index 86%\n" +
|
"similarity index 86%\n" +
|
||||||
"rename from more-index.js\n" +
|
"rename from more-index.js\n" +
|
||||||
"rename to other-index.js\n";
|
"rename to other-index.js\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(0).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(0);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("more-index.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("more-index.js");
|
||||||
expect("other-index.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("other-index.js");
|
||||||
expect(0).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(0);
|
||||||
expect(true).toEqual(file1.isRename);
|
expect(file1.isRename).toEqual(true);
|
||||||
expect("86").toEqual(file1.unchangedPercentage);
|
expect(file1.unchangedPercentage).toEqual(86);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse diffs correct line numbers", function() {
|
it("should parse diffs correct line numbers", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\n" +
|
"diff --git a/sample b/sample\n" +
|
||||||
"index 0000001..0ddf2ba\n" +
|
"index 0000001..0ddf2ba\n" +
|
||||||
|
|
@ -381,23 +381,23 @@ describe("DiffParser", function() {
|
||||||
"-test\n" +
|
"-test\n" +
|
||||||
"+test1r\n";
|
"+test1r\n";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample");
|
||||||
expect("sample").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(2).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(2);
|
||||||
expect(1).toEqual(file1.blocks[0].lines[0].oldNumber);
|
expect(file1.blocks[0].lines[0].oldNumber).toEqual(1);
|
||||||
expect(null).toEqual(file1.blocks[0].lines[0].newNumber);
|
expect(file1.blocks[0].lines[0].newNumber).toBeUndefined();
|
||||||
expect(null).toEqual(file1.blocks[0].lines[1].oldNumber);
|
expect(file1.blocks[0].lines[1].oldNumber).toBeUndefined();
|
||||||
expect(1).toEqual(file1.blocks[0].lines[1].newNumber);
|
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 = [
|
const diffs = [
|
||||||
// 2 hours ahead of GMT
|
// 2 hours ahead of GMT
|
||||||
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
|
"--- a/sample.js 2016-10-25 11:37:14.000000000 +0200\n" +
|
||||||
|
|
@ -416,42 +416,42 @@ describe("DiffParser", function() {
|
||||||
];
|
];
|
||||||
|
|
||||||
diffs.forEach(function(diff) {
|
diffs.forEach(function(diff) {
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample.js");
|
||||||
expect("sample.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
|
|
||||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
"--- a/sample.js\n" + "+++ b/sample.js\n" + "@@ -1 +1,2 @@\n" + "-test\n" + "+test1r\n" + "+test2r\n";
|
"--- 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];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample.js");
|
||||||
expect("sample.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
|
|
||||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
"--- sample.js\n" +
|
"--- sample.js\n" +
|
||||||
"+++ sample.js\n" +
|
"+++ sample.js\n" +
|
||||||
|
|
@ -464,40 +464,40 @@ describe("DiffParser", function() {
|
||||||
"@@ -1 +1,2 @@\n" +
|
"@@ -1 +1,2 @@\n" +
|
||||||
"+test1";
|
"+test1";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(2).toEqual(result.length);
|
expect(result.length).toEqual(2);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample.js");
|
||||||
expect("sample.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample.js");
|
||||||
expect(2).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(2);
|
||||||
|
|
||||||
const linesContent1 = file1.blocks[0].lines.map(function(line) {
|
const linesContent1 = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
return line.content;
|
||||||
});
|
});
|
||||||
expect(linesContent1).toEqual(["-test"]);
|
expect(["-test"]).toEqual(linesContent1);
|
||||||
|
|
||||||
const linesContent2 = file1.blocks[1].lines.map(function(line) {
|
const linesContent2 = file1.blocks[1].lines.map(function(line) {
|
||||||
return line.content;
|
return line.content;
|
||||||
});
|
});
|
||||||
expect(linesContent2).toEqual(["+test"]);
|
expect(["+test"]).toEqual(linesContent2);
|
||||||
|
|
||||||
const file2 = result[1];
|
const file2 = result[1];
|
||||||
expect(1).toEqual(file2.addedLines);
|
expect(file2.addedLines).toEqual(1);
|
||||||
expect(0).toEqual(file2.deletedLines);
|
expect(file2.deletedLines).toEqual(0);
|
||||||
expect("sample1.js").toEqual(file2.oldName);
|
expect(file2.oldName).toEqual("sample1.js");
|
||||||
expect("sample1.js").toEqual(file2.newName);
|
expect(file2.newName).toEqual("sample1.js");
|
||||||
expect(1).toEqual(file2.blocks.length);
|
expect(file2.blocks.length).toEqual(1);
|
||||||
|
|
||||||
const linesContent = file2.blocks[0].lines.map(function(line) {
|
const linesContent = file2.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
"--- sample.js\n" +
|
"--- sample.js\n" +
|
||||||
"+++ sample.js\n" +
|
"+++ sample.js\n" +
|
||||||
|
|
@ -512,40 +512,40 @@ describe("DiffParser", function() {
|
||||||
"+++ 2\n" +
|
"+++ 2\n" +
|
||||||
"++++ 2";
|
"++++ 2";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(3).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(3);
|
||||||
expect(3).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(3);
|
||||||
expect("sample.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample.js");
|
||||||
expect("sample.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
|
|
||||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 diff = "--- sample.js\n" + "+++ sample.js\n" + "@@ @@\n" + " test";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(0).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(0);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("sample.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample.js");
|
||||||
expect("sample.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
|
|
||||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
"diff --git a/last-changes-config.png b/last-changes-config.png\n" +
|
"diff --git a/last-changes-config.png b/last-changes-config.png\n" +
|
||||||
"index 322248b..56fc1f2 100644\n" +
|
"index 322248b..56fc1f2 100644\n" +
|
||||||
|
|
@ -553,19 +553,19 @@ describe("DiffParser", function() {
|
||||||
"+++ b/last-changes-config.png\n" +
|
"+++ b/last-changes-config.png\n" +
|
||||||
"Binary files differ";
|
"Binary files differ";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(0).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(0);
|
||||||
expect(0).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(0);
|
||||||
expect("last-changes-config.png").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("last-changes-config.png");
|
||||||
expect("last-changes-config.png").toEqual(file1.newName);
|
expect(file1.newName).toEqual("last-changes-config.png");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(0).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||||
expect("Binary files differ").toEqual(file1.blocks[0].header);
|
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 =
|
const diff =
|
||||||
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
"diff --git a/src/test-bar.js b/src/test-baz.js\n" +
|
||||||
"similarity index 98%\n" +
|
"similarity index 98%\n" +
|
||||||
|
|
@ -581,22 +581,22 @@ describe("DiffParser", function() {
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ";
|
" ";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("src/test-bar.js").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("src/test-bar.js");
|
||||||
expect("src/test-baz.js").toEqual(file1.newName);
|
expect(file1.newName).toEqual("src/test-baz.js");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(5).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(5);
|
||||||
const linesContent = file1.blocks[0].lines.map(function(line) {
|
const linesContent = file1.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
|
'diff --git "\tTest.scala" "\tScalaTest.scala"\n' +
|
||||||
"similarity index 88%\n" +
|
"similarity index 88%\n" +
|
||||||
|
|
@ -640,36 +640,36 @@ describe("DiffParser", function() {
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ";
|
" ";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
const result = parse(diff, { srcPrefix: "\t", dstPrefix: "\t" });
|
||||||
expect(3).toEqual(result.length);
|
expect(result.length).toEqual(3);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(2).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(2);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("Test.scala").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("Test.scala");
|
||||||
expect("ScalaTest.scala").toEqual(file1.newName);
|
expect(file1.newName).toEqual("ScalaTest.scala");
|
||||||
expect(2).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(2);
|
||||||
expect(8).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(8);
|
||||||
expect(7).toEqual(file1.blocks[1].lines.length);
|
expect(file1.blocks[1].lines.length).toEqual(7);
|
||||||
|
|
||||||
const file2 = result[1];
|
const file2 = result[1];
|
||||||
expect("/dev/null").toEqual(file2.oldName);
|
expect(file2.oldName).toEqual("/dev/null");
|
||||||
expect("tardis.png").toEqual(file2.newName);
|
expect(file2.newName).toEqual("tardis.png");
|
||||||
|
|
||||||
const file3 = result[2];
|
const file3 = result[2];
|
||||||
expect(1).toEqual(file3.addedLines);
|
expect(file3.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file3.deletedLines);
|
expect(file3.deletedLines).toEqual(1);
|
||||||
expect("src/test-bar.js").toEqual(file3.oldName);
|
expect(file3.oldName).toEqual("src/test-bar.js");
|
||||||
expect("src/test-baz.js").toEqual(file3.newName);
|
expect(file3.newName).toEqual("src/test-baz.js");
|
||||||
expect(1).toEqual(file3.blocks.length);
|
expect(file3.blocks.length).toEqual(1);
|
||||||
expect(5).toEqual(file3.blocks[0].lines.length);
|
expect(file3.blocks[0].lines.length).toEqual(5);
|
||||||
const linesContent = file3.blocks[0].lines.map(function(line) {
|
const linesContent = file3.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diff =
|
||||||
"diff --git a/favicon.png b/favicon.png\n" +
|
"diff --git a/favicon.png b/favicon.png\n" +
|
||||||
"deleted file mode 100644\n" +
|
"deleted file mode 100644\n" +
|
||||||
|
|
@ -703,26 +703,26 @@ describe("DiffParser", function() {
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" ";
|
" ";
|
||||||
|
|
||||||
const result = DiffParser.generateDiffJson(diff);
|
const result = parse(diff);
|
||||||
expect(2).toEqual(result.length);
|
expect(result.length).toEqual(2);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect("favicon.png").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("favicon.png");
|
||||||
expect("favicon.png").toEqual(file1.newName);
|
expect(file1.newName).toEqual("favicon.png");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
expect(0).toEqual(file1.blocks[0].lines.length);
|
expect(file1.blocks[0].lines.length).toEqual(0);
|
||||||
|
|
||||||
const file2 = result[1];
|
const file2 = result[1];
|
||||||
expect(1).toEqual(file2.addedLines);
|
expect(file2.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file2.deletedLines);
|
expect(file2.deletedLines).toEqual(1);
|
||||||
expect("src/test-bar.js").toEqual(file2.oldName);
|
expect(file2.oldName).toEqual("src/test-bar.js");
|
||||||
expect("src/test-baz.js").toEqual(file2.newName);
|
expect(file2.newName).toEqual("src/test-baz.js");
|
||||||
expect(1).toEqual(file2.blocks.length);
|
expect(file2.blocks.length).toEqual(1);
|
||||||
expect(5).toEqual(file2.blocks[0].lines.length);
|
expect(file2.blocks[0].lines.length).toEqual(5);
|
||||||
const linesContent = file2.blocks[0].lines.map(function(line) {
|
const linesContent = file2.blocks[0].lines.map(function(line) {
|
||||||
return line.content;
|
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 =
|
const diffExample1 =
|
||||||
"diff --git a/sample b/sample\n" +
|
"diff --git a/sample b/sample\n" +
|
||||||
|
|
@ -9,27 +10,27 @@ const diffExample1 =
|
||||||
"-test\n" +
|
"-test\n" +
|
||||||
"+test1\n";
|
"+test1\n";
|
||||||
|
|
||||||
const jsonExample1 = [
|
const jsonExample1: DiffFile[] = [
|
||||||
{
|
{
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+test1",
|
content: "+test1",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
oldStartLine: "1",
|
oldStartLine: 1,
|
||||||
oldStartLine2: null,
|
oldStartLine2: undefined,
|
||||||
newStartLine: "1",
|
newStartLine: 1,
|
||||||
header: "@@ -1 +1 @@"
|
header: "@@ -1 +1 @@"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -38,9 +39,10 @@ const jsonExample1 = [
|
||||||
checksumBefore: "0000001",
|
checksumBefore: "0000001",
|
||||||
checksumAfter: "0ddf2ba",
|
checksumAfter: "0ddf2ba",
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: undefined,
|
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
language: "",
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -183,9 +185,9 @@ const htmlSideExample1 =
|
||||||
|
|
||||||
const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
|
const htmlSideExample1WithFilesSummary = filesExample1 + htmlSideExample1;
|
||||||
|
|
||||||
describe("Diff2Html", function() {
|
describe("Diff2Html", () => {
|
||||||
describe("getJsonFromDiff", function() {
|
describe("getJsonFromDiff", () => {
|
||||||
it("should parse simple diff to json", function() {
|
it("should parse simple diff to json", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample b/sample\n" +
|
"diff --git a/sample b/sample\n" +
|
||||||
"index 0000001..0ddf2ba\n" +
|
"index 0000001..0ddf2ba\n" +
|
||||||
|
|
@ -194,19 +196,19 @@ describe("Diff2Html", function() {
|
||||||
"@@ -1 +1 @@\n" +
|
"@@ -1 +1 @@\n" +
|
||||||
"-test\n" +
|
"-test\n" +
|
||||||
"+test1\n";
|
"+test1\n";
|
||||||
const result = Diff2Html.getJsonFromDiff(diff);
|
const result = parse(diff);
|
||||||
|
|
||||||
const file1 = result[0];
|
const file1 = result[0];
|
||||||
expect(1).toEqual(result.length);
|
expect(result.length).toEqual(1);
|
||||||
expect(1).toEqual(file1.addedLines);
|
expect(file1.addedLines).toEqual(1);
|
||||||
expect(1).toEqual(file1.deletedLines);
|
expect(file1.deletedLines).toEqual(1);
|
||||||
expect("sample").toEqual(file1.oldName);
|
expect(file1.oldName).toEqual("sample");
|
||||||
expect("sample").toEqual(file1.newName);
|
expect(file1.newName).toEqual("sample");
|
||||||
expect(1).toEqual(file1.blocks.length);
|
expect(file1.blocks.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test case for issue #49
|
// Test case for issue #49
|
||||||
it("should parse diff with added EOF", function() {
|
it("should parse diff with added EOF", () => {
|
||||||
const diff =
|
const diff =
|
||||||
"diff --git a/sample.scala b/sample.scala\n" +
|
"diff --git a/sample.scala b/sample.scala\n" +
|
||||||
"index b583263..8b2fc3e 100644\n" +
|
"index b583263..8b2fc3e 100644\n" +
|
||||||
|
|
@ -223,67 +225,67 @@ describe("Diff2Html", function() {
|
||||||
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
|
"+ IndexLock, RepositoryError, NotValidRepo, PullRequestNotMergeable, BranchError,\n" +
|
||||||
"+ PluginError, CodeParserError, EngineError = Value\n" +
|
"+ PluginError, CodeParserError, EngineError = Value\n" +
|
||||||
"+}\n";
|
"+}\n";
|
||||||
const result = Diff2Html.getJsonFromDiff(diff);
|
const result = parse(diff);
|
||||||
|
|
||||||
expect(50).toEqual(result[0].blocks[0].lines[0].oldNumber);
|
expect(result[0].blocks[0].lines[0].oldNumber).toEqual(50);
|
||||||
expect(50).toEqual(result[0].blocks[0].lines[0].newNumber);
|
expect(result[0].blocks[0].lines[0].newNumber).toEqual(50);
|
||||||
|
|
||||||
expect(51).toEqual(result[0].blocks[0].lines[1].oldNumber);
|
expect(result[0].blocks[0].lines[1].oldNumber).toEqual(51);
|
||||||
expect(51).toEqual(result[0].blocks[0].lines[1].newNumber);
|
expect(result[0].blocks[0].lines[1].newNumber).toEqual(51);
|
||||||
|
|
||||||
expect(52).toEqual(result[0].blocks[0].lines[2].oldNumber);
|
expect(result[0].blocks[0].lines[2].oldNumber).toEqual(52);
|
||||||
expect(52).toEqual(result[0].blocks[0].lines[2].newNumber);
|
expect(result[0].blocks[0].lines[2].newNumber).toEqual(52);
|
||||||
|
|
||||||
expect(53).toEqual(result[0].blocks[0].lines[3].oldNumber);
|
expect(result[0].blocks[0].lines[3].oldNumber).toEqual(53);
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[3].newNumber);
|
expect(result[0].blocks[0].lines[3].newNumber).toBeUndefined();
|
||||||
|
|
||||||
expect(54).toEqual(result[0].blocks[0].lines[4].oldNumber);
|
expect(result[0].blocks[0].lines[4].oldNumber).toEqual(54);
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[4].newNumber);
|
expect(result[0].blocks[0].lines[4].newNumber).toBeUndefined();
|
||||||
|
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[5].oldNumber);
|
expect(result[0].blocks[0].lines[5].oldNumber).toBeUndefined();
|
||||||
expect(53).toEqual(result[0].blocks[0].lines[5].newNumber);
|
expect(result[0].blocks[0].lines[5].newNumber).toEqual(53);
|
||||||
|
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[6].oldNumber);
|
expect(result[0].blocks[0].lines[6].oldNumber).toBeUndefined();
|
||||||
expect(54).toEqual(result[0].blocks[0].lines[6].newNumber);
|
expect(result[0].blocks[0].lines[6].newNumber).toEqual(54);
|
||||||
|
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[7].oldNumber);
|
expect(result[0].blocks[0].lines[7].oldNumber).toBeUndefined();
|
||||||
expect(55).toEqual(result[0].blocks[0].lines[7].newNumber);
|
expect(result[0].blocks[0].lines[7].newNumber).toEqual(55);
|
||||||
|
|
||||||
expect(null).toEqual(result[0].blocks[0].lines[8].oldNumber);
|
expect(result[0].blocks[0].lines[8].oldNumber).toBeUndefined();
|
||||||
expect(56).toEqual(result[0].blocks[0].lines[8].newNumber);
|
expect(result[0].blocks[0].lines[8].newNumber).toEqual(56);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty line by line html from diff", function() {
|
it("should generate pretty line by line html from diff", () => {
|
||||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1);
|
const result = html(diffExample1);
|
||||||
expect(htmlLineExample1).toEqual(result);
|
expect(result).toEqual(htmlLineExample1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty line by line html from json", function() {
|
it("should generate pretty line by line html from json", () => {
|
||||||
const result = Diff2Html.getPrettyHtmlFromJson(jsonExample1);
|
const result = html(jsonExample1);
|
||||||
expect(htmlLineExample1).toEqual(result);
|
expect(result).toEqual(htmlLineExample1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty diff with files summary", function() {
|
it("should generate pretty diff with files summary", () => {
|
||||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample1, { showFiles: true });
|
const result = html(diffExample1, { showFiles: true });
|
||||||
expect(htmlLineExample1WithFilesSummary).toEqual(result);
|
expect(result).toEqual(htmlLineExample1WithFilesSummary);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty side by side html from diff", function() {
|
it("should generate pretty side by side html from diff", () => {
|
||||||
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1);
|
const result = html(diffExample1, { outputFormat: "side-by-side" });
|
||||||
expect(htmlSideExample1).toEqual(result);
|
expect(result).toEqual(htmlSideExample1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty side by side html from json", function() {
|
it("should generate pretty side by side html from json", () => {
|
||||||
const result = Diff2Html.getPrettySideBySideHtmlFromJson(jsonExample1);
|
const result = html(jsonExample1, { outputFormat: "side-by-side" });
|
||||||
expect(htmlSideExample1).toEqual(result);
|
expect(result).toEqual(htmlSideExample1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should generate pretty side by side html from diff 2", function() {
|
it("should generate pretty side by side html from diff 2", () => {
|
||||||
const result = Diff2Html.getPrettySideBySideHtmlFromDiff(diffExample1, { showFiles: true });
|
const result = html(diffExample1, { outputFormat: "side-by-side", showFiles: true });
|
||||||
expect(htmlSideExample1WithFilesSummary).toEqual(result);
|
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 =
|
const diffExample2 =
|
||||||
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
|
"diff --git a/CHANGELOG.md b/CHANGELOG.md\n" +
|
||||||
"index fc3e3f4..b486d10 100644\n" +
|
"index fc3e3f4..b486d10 100644\n" +
|
||||||
|
|
@ -516,8 +518,8 @@ describe("Diff2Html", function() {
|
||||||
"</div>\n" +
|
"</div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
const result = Diff2Html.getPrettyHtmlFromDiff(diffExample2);
|
const result = html(diffExample2);
|
||||||
expect(result).toEqual(htmlExample2);
|
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("FileListPrinter", () => {
|
||||||
describe("generateFileList", function() {
|
describe("generateFileList", () => {
|
||||||
it("should expose old and new files to templates", function() {
|
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 = [
|
const files = [
|
||||||
{
|
{
|
||||||
addedlines: 12,
|
isCombined: false,
|
||||||
deletedlines: 41,
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
|
addedLines: 12,
|
||||||
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
oldName: "my/file/name.js",
|
oldName: "my/file/name.js",
|
||||||
newName: "my/file/name.js"
|
newName: "my/file/name.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -19,6 +32,9 @@ describe("FileListPrinter", function() {
|
||||||
newName: "my/file/name2.js"
|
newName: "my/file/name2.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 0,
|
deletedLines: 0,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -27,6 +43,9 @@ describe("FileListPrinter", function() {
|
||||||
isNew: true
|
isNew: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 0,
|
addedLines: 0,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -36,26 +55,23 @@ describe("FileListPrinter", function() {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const fileListPrinter = new FileListPrinter({
|
const fileHtml = render(files, hoganUtils);
|
||||||
rawTemplates: {
|
|
||||||
"file-summary-wrapper": "{{{files}}}",
|
|
||||||
"file-summary-line": "{{oldName}}, {{newName}}, {{fileName}}"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileHtml = fileListPrinter.generateFileList(files);
|
|
||||||
const expected =
|
const expected =
|
||||||
"my/file/name.js, my/file/name.js, my/file/name.js\n" +
|
"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" +
|
"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" +
|
"dev/null, my/file/name.js, my/file/name.js\n" +
|
||||||
"my/file/name.js, dev/null, my/file/name.js";
|
"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 = [
|
const files = [
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -63,6 +79,9 @@ describe("FileListPrinter", function() {
|
||||||
newName: "my/file/name.js"
|
newName: "my/file/name.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -70,6 +89,9 @@ describe("FileListPrinter", function() {
|
||||||
newName: "my/file/name2.js"
|
newName: "my/file/name2.js"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 0,
|
deletedLines: 0,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -78,6 +100,9 @@ describe("FileListPrinter", function() {
|
||||||
isNew: true
|
isNew: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: [],
|
||||||
addedLines: 0,
|
addedLines: 0,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
|
|
@ -87,8 +112,7 @@ describe("FileListPrinter", function() {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const fileListPrinter = new FileListPrinter();
|
const fileHtml = render(files, hoganUtils);
|
||||||
const fileHtml = fileListPrinter.generateFileList(files);
|
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<div class="d2h-file-list-wrapper">\n' +
|
'<div class="d2h-file-list-wrapper">\n' +
|
||||||
|
|
@ -149,7 +173,7 @@ describe("FileListPrinter", function() {
|
||||||
" </ol>\n" +
|
" </ol>\n" +
|
||||||
"</div>";
|
"</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("LineByLineRenderer", () => {
|
||||||
describe("_generateEmptyDiff", function() {
|
describe("_generateEmptyDiff", () => {
|
||||||
it("should return an empty diff", function() {
|
it("should return an empty diff", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const fileHtml = lineByLinePrinter._generateEmptyDiff();
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
const fileHtml = lineByLineRenderer.generateEmptyDiff();
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
' <td class="d2h-info">\n' +
|
' <td class="d2h-info">\n' +
|
||||||
|
|
@ -14,15 +17,15 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("makeLineHtml", function() {
|
describe("makeLineHtml", () => {
|
||||||
it("should work for insertions", function() {
|
it("should work for insertions", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "test", "+");
|
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "test", undefined, 30, "+");
|
||||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -38,13 +41,13 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should work for deletions", function() {
|
it("should work for deletions", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "", "test", "-");
|
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.DELETES, "test", 30, undefined, "-");
|
||||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -60,13 +63,13 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should convert indents into non breakin spaces (2 white spaces)", function() {
|
it("should convert indents into non breakin spaces (2 white spaces)", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+");
|
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
|
||||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -82,13 +85,13 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should convert indents into non breakin spaces (4 white spaces)", function() {
|
it("should convert indents into non breakin spaces (4 white spaces)", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, " test", "+");
|
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, " test", undefined, 30, "+");
|
||||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -104,13 +107,13 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should preserve tabs", function() {
|
it("should preserve tabs", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
let fileHtml = lineByLinePrinter.makeLineHtml(false, diffParser.LINE_TYPE.INSERTS, "", 30, "\ttest", "+");
|
let fileHtml = lineByLineRenderer.makeLineHtml(false, CSSLineClass.INSERTS, "\ttest", undefined, 30, "+");
|
||||||
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
fileHtml = fileHtml.replace(/\n\n+/g, "\n");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -127,24 +130,28 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("makeFileDiffHtml", function() {
|
describe("makeFileDiffHtml", () => {
|
||||||
it("should work for simple file", function() {
|
it("should work for simple file", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
deletedLines: 41,
|
deletedLines: 41,
|
||||||
language: "js",
|
language: "js",
|
||||||
oldName: "my/file/name.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 diffs = "<span>Random Html</span>";
|
||||||
|
|
||||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -166,10 +173,11 @@ describe("LineByLinePrinter", function() {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for simple added file", function() {
|
it("should work for simple added file", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
|
|
@ -177,11 +185,14 @@ describe("LineByLinePrinter", function() {
|
||||||
language: "js",
|
language: "js",
|
||||||
oldName: "dev/null",
|
oldName: "dev/null",
|
||||||
newName: "my/file/name.js",
|
newName: "my/file/name.js",
|
||||||
isNew: true
|
isNew: true,
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: []
|
||||||
};
|
};
|
||||||
const diffs = "<span>Random Html</span>";
|
const diffs = "<span>Random Html</span>";
|
||||||
|
|
||||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -203,10 +214,11 @@ describe("LineByLinePrinter", function() {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for simple deleted file", function() {
|
it("should work for simple deleted file", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
addedLines: 0,
|
addedLines: 0,
|
||||||
|
|
@ -214,11 +226,14 @@ describe("LineByLinePrinter", function() {
|
||||||
language: "js",
|
language: "js",
|
||||||
oldName: "my/file/name.js",
|
oldName: "my/file/name.js",
|
||||||
newName: "dev/null",
|
newName: "dev/null",
|
||||||
isDeleted: true
|
isDeleted: true,
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: []
|
||||||
};
|
};
|
||||||
const diffs = "<span>Random Html</span>";
|
const diffs = "<span>Random Html</span>";
|
||||||
|
|
||||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
'<div id="d2h-781444" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -240,10 +255,11 @@ describe("LineByLinePrinter", function() {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for simple renamed file", function() {
|
it("should work for simple renamed file", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
addedLines: 12,
|
addedLines: 12,
|
||||||
|
|
@ -251,11 +267,14 @@ describe("LineByLinePrinter", function() {
|
||||||
language: "js",
|
language: "js",
|
||||||
oldName: "my/file/name1.js",
|
oldName: "my/file/name1.js",
|
||||||
newName: "my/file/name2.js",
|
newName: "my/file/name2.js",
|
||||||
isRename: true
|
isRename: true,
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: false,
|
||||||
|
blocks: []
|
||||||
};
|
};
|
||||||
const diffs = "<span>Random Html</span>";
|
const diffs = "<span>Random Html</span>";
|
||||||
|
|
||||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
|
'<div id="d2h-662683" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -277,61 +296,71 @@ describe("LineByLinePrinter", function() {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", function() {
|
it("should return empty when option renderNothingWhenEmpty is true and file blocks not present", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||||
renderNothingWhenEmpty: true
|
renderNothingWhenEmpty: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const file = {
|
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: []
|
blocks: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const diffs = "<span>Random Html</span>";
|
const diffs = "<span>Random Html</span>";
|
||||||
|
|
||||||
const fileHtml = lineByLinePrinter.makeFileDiffHtml(file, diffs);
|
const fileHtml = lineByLineRenderer.makeFileDiffHtml(file, diffs);
|
||||||
|
|
||||||
const expected = "";
|
const expected = "";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("makeLineByLineHtmlWrapper", function() {
|
describe("makeLineByLineHtmlWrapper", () => {
|
||||||
it("should work for simple content", function() {
|
it("should work for simple content", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const fileHtml = lineByLinePrinter.makeLineByLineHtmlWrapper("<span>Random Html</span>");
|
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>";
|
const expected = '<div class="d2h-wrapper">\n' + " <span>Random Html</span>\n" + "</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("generateLineByLineJsonHtml", function() {
|
describe("generateLineByLineJsonHtml", () => {
|
||||||
it("should work for list of files", function() {
|
it("should work for list of files", () => {
|
||||||
const exampleJson = [
|
const exampleJson: DiffFile[] = [
|
||||||
{
|
{
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+test1r",
|
content: "+test1r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
oldStartLine: "1",
|
oldStartLine: 1,
|
||||||
oldStartLine2: null,
|
oldStartLine2: undefined,
|
||||||
newStartLine: "1",
|
newStartLine: 1,
|
||||||
header: "@@ -1 +1 @@"
|
header: "@@ -1 +1 @@"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -340,17 +369,21 @@ describe("LineByLinePrinter", function() {
|
||||||
checksumBefore: "0000001",
|
checksumBefore: "0000001",
|
||||||
checksumAfter: "0ddf2ba",
|
checksumAfter: "0ddf2ba",
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: undefined,
|
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
language: "txt",
|
||||||
|
isCombined: false,
|
||||||
|
isGitDiff: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const lineByLinePrinter = new LineByLinePrinter({ matching: "lines" });
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||||
|
matching: "lines"
|
||||||
|
});
|
||||||
|
const html = lineByLineRenderer.render(exampleJson);
|
||||||
const expected =
|
const expected =
|
||||||
'<div class="d2h-wrapper">\n' +
|
'<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' +
|
' <div class="d2h-file-header">\n' +
|
||||||
' <span class="d2h-file-name-wrapper">\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' +
|
' <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' +
|
' <div class="d2h-code-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\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-num1">1</div>\n' +
|
||||||
'<div class="line-num2"></div>\n' +
|
'<div class="line-num2"></div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-del">\n' +
|
' <td class="d2h-del d2h-change">\n' +
|
||||||
' <div class="d2h-code-line d2h-del">\n' +
|
' <div class="d2h-code-line d2h-del d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\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-num1"></div>\n' +
|
||||||
'<div class="line-num2">1</div>\n' +
|
'<div class="line-num2">1</div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-ins">\n' +
|
' <td class="d2h-ins d2h-change">\n' +
|
||||||
' <div class="d2h-code-line d2h-ins">\n' +
|
' <div class="d2h-code-line d2h-ins d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -397,10 +430,10 @@ describe("LineByLinePrinter", function() {
|
||||||
"</div>\n" +
|
"</div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(html);
|
expect(html).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should work for empty blocks", function() {
|
it("should work for empty blocks", () => {
|
||||||
const exampleJson = [
|
const exampleJson = [
|
||||||
{
|
{
|
||||||
blocks: [],
|
blocks: [],
|
||||||
|
|
@ -409,12 +442,16 @@ describe("LineByLinePrinter", function() {
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: "js",
|
language: "js",
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
isCombined: false,
|
||||||
|
isGitDiff: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const lineByLinePrinter = new LineByLinePrinter({ renderNothingWhenEmpty: false });
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const html = lineByLinePrinter.generateLineByLineJsonHtml(exampleJson);
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {
|
||||||
|
renderNothingWhenEmpty: false
|
||||||
|
});
|
||||||
|
const html = lineByLineRenderer.render(exampleJson);
|
||||||
const expected =
|
const expected =
|
||||||
'<div class="d2h-wrapper">\n' +
|
'<div class="d2h-wrapper">\n' +
|
||||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -443,31 +480,32 @@ describe("LineByLinePrinter", function() {
|
||||||
"</div>\n" +
|
"</div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(html);
|
expect(html).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("_processLines", function() {
|
describe("_processLines", () => {
|
||||||
it("should work for simple block header", function() {
|
it("should work for simple block header", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const oldLines = [
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
const oldLines: DiffLine[] = [
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const newLines = [
|
const newLines: DiffLine[] = [
|
||||||
{
|
{
|
||||||
content: "+test1r",
|
content: "+test1r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const html = lineByLinePrinter._processLines(false, oldLines, newLines);
|
const html = lineByLineRenderer.processLines(false, oldLines, newLines);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -494,45 +532,46 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(html);
|
expect(html).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("_generateFileHtml", function() {
|
describe("_generateFileHtml", () => {
|
||||||
it("should work for simple file", function() {
|
it("should work for simple file", () => {
|
||||||
const lineByLinePrinter = new LineByLinePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const file = {
|
const lineByLineRenderer = new LineByLineRenderer(hoganUtils, {});
|
||||||
|
const file: DiffFile = {
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
content: " one context line",
|
content: " one context line",
|
||||||
type: "d2h-cntx",
|
type: LineType.CONTEXT,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 2,
|
oldNumber: 2,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+test1r",
|
content: "+test1r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 2
|
newNumber: 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+test2r",
|
content: "+test2r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 3
|
newNumber: 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
oldStartLine: "1",
|
oldStartLine: 1,
|
||||||
oldStartLine2: null,
|
oldStartLine2: undefined,
|
||||||
newStartLine: "1",
|
newStartLine: 1,
|
||||||
header: "@@ -1 +1 @@"
|
header: "@@ -1 +1 @@"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -541,12 +580,13 @@ describe("LineByLinePrinter", function() {
|
||||||
checksumBefore: "0000001",
|
checksumBefore: "0000001",
|
||||||
checksumAfter: "0ddf2ba",
|
checksumAfter: "0ddf2ba",
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: undefined,
|
language: "txt",
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
isCombined: false,
|
||||||
|
isGitDiff: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const html = lineByLinePrinter._generateFileHtml(file);
|
const html = lineByLineRenderer.generateFileHtml(file);
|
||||||
|
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -600,7 +640,7 @@ describe("LineByLinePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</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("Utils", function() {
|
||||||
describe("getHtmlId", function() {
|
describe("getHtmlId", function() {
|
||||||
it("should generate file unique id", function() {
|
it("should generate file unique id", function() {
|
||||||
const result = PrinterUtils.getHtmlId({
|
const result = renderUtils.getHtmlId({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
expect("d2h-960013").toEqual(result);
|
expect("d2h-960013").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate file unique id for empty hashes", function() {
|
it("should generate file unique id for empty hashes", function() {
|
||||||
const result = PrinterUtils.getHtmlId({
|
const result = renderUtils.getHtmlId({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
|
|
@ -20,105 +20,102 @@ describe("Utils", function() {
|
||||||
|
|
||||||
describe("getDiffName", function() {
|
describe("getDiffName", function() {
|
||||||
it("should generate the file name for a changed file", function() {
|
it("should generate the file name for a changed file", function() {
|
||||||
const result = PrinterUtils.getDiffName({
|
const result = renderUtils.filenameDiff({
|
||||||
oldName: "sample.js",
|
oldName: "sample.js",
|
||||||
newName: "sample.js"
|
newName: "sample.js"
|
||||||
});
|
});
|
||||||
expect("sample.js").toEqual(result);
|
expect("sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and full rename", function() {
|
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",
|
oldName: "sample1.js",
|
||||||
newName: "sample2.js"
|
newName: "sample2.js"
|
||||||
});
|
});
|
||||||
expect("sample1.js → sample2.js").toEqual(result);
|
expect("sample1.js → sample2.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and prefix rename", function() {
|
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",
|
oldName: "src/path/sample.js",
|
||||||
newName: "source/path/sample.js"
|
newName: "source/path/sample.js"
|
||||||
});
|
});
|
||||||
expect("{src → source}/path/sample.js").toEqual(result);
|
expect("{src → source}/path/sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and suffix rename", function() {
|
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",
|
oldName: "src/path/sample1.js",
|
||||||
newName: "src/path/sample2.js"
|
newName: "src/path/sample2.js"
|
||||||
});
|
});
|
||||||
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
expect("src/path/{sample1.js → sample2.js}").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a changed file and middle rename", function() {
|
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",
|
oldName: "src/really/big/path/sample.js",
|
||||||
newName: "src/small/path/sample.js"
|
newName: "src/small/path/sample.js"
|
||||||
});
|
});
|
||||||
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
expect("src/{really/big → small}/path/sample.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a deleted file", function() {
|
it("should generate the file name for a deleted file", function() {
|
||||||
const result = PrinterUtils.getDiffName({
|
const result = renderUtils.filenameDiff({
|
||||||
oldName: "src/my/file.js",
|
oldName: "src/my/file.js",
|
||||||
newName: "/dev/null"
|
newName: "/dev/null"
|
||||||
});
|
});
|
||||||
expect("src/my/file.js").toEqual(result);
|
expect("src/my/file.js").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should generate the file name for a new file", function() {
|
it("should generate the file name for a new file", function() {
|
||||||
const result = PrinterUtils.getDiffName({
|
const result = renderUtils.filenameDiff({
|
||||||
oldName: "/dev/null",
|
oldName: "/dev/null",
|
||||||
newName: "src/my/file.js"
|
newName: "src/my/file.js"
|
||||||
});
|
});
|
||||||
expect("src/my/file.js").toEqual(result);
|
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() {
|
describe("diffHighlight", function() {
|
||||||
it("should highlight two lines", 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({
|
expect(result).toEqual({
|
||||||
first: {
|
oldLine: {
|
||||||
prefix: "-",
|
prefix: "-",
|
||||||
line: "var <del>myVar</del> = <del>2</del>;"
|
content: "var <del>myVar</del> = <del>2</del>;"
|
||||||
},
|
},
|
||||||
second: {
|
newLine: {
|
||||||
prefix: "+",
|
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() {
|
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({
|
expect({
|
||||||
first: {
|
oldLine: {
|
||||||
prefix: "-",
|
prefix: "-",
|
||||||
line: "var myVar = <del>2</del>;"
|
content: "var myVar = <del>2</del>;"
|
||||||
},
|
},
|
||||||
second: {
|
newLine: {
|
||||||
prefix: "+",
|
prefix: "+",
|
||||||
line: "var myVar<ins>iable</ins> = <ins>3</ins>;"
|
content: "var myVar<ins>iable</ins> = <ins>3</ins>;"
|
||||||
}
|
}
|
||||||
}).toEqual(result);
|
}).toEqual(result);
|
||||||
});
|
});
|
||||||
it("should highlight combined diff lines", function() {
|
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",
|
diffStyle: "word",
|
||||||
isCombined: true,
|
|
||||||
matching: "words",
|
matching: "words",
|
||||||
matchWordsThreshold: 1.0
|
matchWordsThreshold: 1.0
|
||||||
});
|
});
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
first: {
|
oldLine: {
|
||||||
prefix: " -",
|
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: " +",
|
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);
|
}).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("SideBySideRenderer", () => {
|
||||||
describe("generateEmptyDiff", function() {
|
describe("generateEmptyDiff", () => {
|
||||||
it("should return an empty diff", function() {
|
it("should return an empty diff", () => {
|
||||||
const sideBySidePrinter = new SideBySidePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const fileHtml = sideBySidePrinter.generateEmptyDiff();
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
|
const fileHtml = sideBySideRenderer.generateEmptyDiff();
|
||||||
const expectedRight = "";
|
const expectedRight = "";
|
||||||
const expectedLeft =
|
const expectedLeft =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -15,51 +18,53 @@ describe("SideBySidePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expectedRight).toEqual(fileHtml.right);
|
expect(fileHtml.right).toEqual(expectedRight);
|
||||||
expect(expectedLeft).toEqual(fileHtml.left);
|
expect(fileHtml.left).toEqual(expectedLeft);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("generateSideBySideFileHtml", function() {
|
describe("generateSideBySideFileHtml", () => {
|
||||||
it("should generate lines with the right prefixes", function() {
|
it("should generate lines with the right prefixes", () => {
|
||||||
const sideBySidePrinter = new SideBySidePrinter({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
|
|
||||||
const file = {
|
const file: DiffFile = {
|
||||||
|
isGitDiff: true,
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
content: " context",
|
content: " context",
|
||||||
type: "d2h-cntx",
|
type: LineType.CONTEXT,
|
||||||
oldNumber: 19,
|
oldNumber: 19,
|
||||||
newNumber: 19
|
newNumber: 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "-removed",
|
content: "-removed",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 20,
|
oldNumber: 20,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+added",
|
content: "+added",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 20
|
newNumber: 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+another added",
|
content: "+another added",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 21
|
newNumber: 21
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
oldStartLine: "19",
|
oldStartLine: 19,
|
||||||
newStartLine: "19",
|
newStartLine: 19,
|
||||||
header: "@@ -19,7 +19,7 @@"
|
header: "@@ -19,7 +19,7 @@"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
deletedLines: 1,
|
deletedLines: 1,
|
||||||
addedLines: 1,
|
addedLines: 2,
|
||||||
checksumBefore: "fc56817",
|
checksumBefore: "fc56817",
|
||||||
checksumAfter: "e8e7e49",
|
checksumAfter: "e8e7e49",
|
||||||
mode: "100644",
|
mode: "100644",
|
||||||
|
|
@ -69,7 +74,7 @@ describe("SideBySidePrinter", function() {
|
||||||
isCombined: false
|
isCombined: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileHtml = sideBySidePrinter.generateSideBySideFileHtml(file);
|
const fileHtml = sideBySideRenderer.generateSideBySideFileHtml(file);
|
||||||
|
|
||||||
const expectedLeft =
|
const expectedLeft =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -148,16 +153,16 @@ describe("SideBySidePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expectedLeft).toEqual(fileHtml.left);
|
expect(fileHtml.left).toEqual(expectedLeft);
|
||||||
expect(expectedRight).toEqual(fileHtml.right);
|
expect(fileHtml.right).toEqual(expectedRight);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("generateSingleLineHtml", function() {
|
describe("generateSingleLineHtml", () => {
|
||||||
it("should work for insertions", function() {
|
it("should work for insertions", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const sideBySidePrinter = new SideBySidePrinter({});
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.INSERTS, 30, "test", "+");
|
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.INSERTS, "test", 30, "+");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||||
|
|
@ -171,12 +176,12 @@ describe("SideBySidePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for deletions", function() {
|
it("should work for deletions", () => {
|
||||||
const diffParser = require("../diff-parser.js").DiffParser;
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const sideBySidePrinter = new SideBySidePrinter({});
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
const fileHtml = sideBySidePrinter.generateSingleLineHtml(false, diffParser.LINE_TYPE.DELETES, 30, "test", "-");
|
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.DELETES, "test", 30, "-");
|
||||||
const expected =
|
const expected =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||||
|
|
@ -190,33 +195,33 @@ describe("SideBySidePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expected).toEqual(fileHtml);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("generateSideBySideJsonHtml", function() {
|
describe("generateSideBySideJsonHtml", () => {
|
||||||
it("should work for list of files", function() {
|
it("should work for list of files", () => {
|
||||||
const exampleJson = [
|
const exampleJson: DiffFile[] = [
|
||||||
{
|
{
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
lines: [
|
lines: [
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "+test1r",
|
content: "+test1r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
oldStartLine: "1",
|
oldStartLine: 1,
|
||||||
oldStartLine2: null,
|
oldStartLine2: undefined,
|
||||||
newStartLine: "1",
|
newStartLine: 1,
|
||||||
header: "@@ -1 +1 @@"
|
header: "@@ -1 +1 @@"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -225,17 +230,19 @@ describe("SideBySidePrinter", function() {
|
||||||
checksumBefore: "0000001",
|
checksumBefore: "0000001",
|
||||||
checksumAfter: "0ddf2ba",
|
checksumAfter: "0ddf2ba",
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: undefined,
|
language: "txt",
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
isCombined: false,
|
||||||
|
isGitDiff: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" });
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
|
||||||
|
const html = sideBySideRenderer.render(exampleJson);
|
||||||
const expected =
|
const expected =
|
||||||
'<div class="d2h-wrapper">\n' +
|
'<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' +
|
' <div class="d2h-file-header">\n' +
|
||||||
' <span class="d2h-file-name-wrapper">\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' +
|
' <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' +
|
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\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" +
|
" 1\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-del">\n' +
|
' <td class="d2h-del d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-del">\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-prefix">-</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -278,11 +285,11 @@ describe("SideBySidePrinter", function() {
|
||||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\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" +
|
" 1\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-ins">\n' +
|
' <td class="d2h-ins d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-ins">\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-prefix">+</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
' <span class="d2h-code-line-ctn"><ins>test1r</ins></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -296,21 +303,25 @@ describe("SideBySidePrinter", function() {
|
||||||
"</div>\n" +
|
"</div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(html);
|
expect(html).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for files without blocks", function() {
|
it("should work for files without blocks", () => {
|
||||||
const exampleJson = [
|
const exampleJson: DiffFile[] = [
|
||||||
{
|
{
|
||||||
blocks: [],
|
blocks: [],
|
||||||
oldName: "sample",
|
oldName: "sample",
|
||||||
language: "js",
|
language: "js",
|
||||||
newName: "sample",
|
newName: "sample",
|
||||||
isCombined: false
|
isCombined: false,
|
||||||
|
addedLines: 0,
|
||||||
|
deletedLines: 0,
|
||||||
|
isGitDiff: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const sideBySidePrinter = new SideBySidePrinter();
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const html = sideBySidePrinter.generateSideBySideJsonHtml(exampleJson);
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
|
const html = sideBySideRenderer.render(exampleJson);
|
||||||
const expected =
|
const expected =
|
||||||
'<div class="d2h-wrapper">\n' +
|
'<div class="d2h-wrapper">\n' +
|
||||||
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
' <div id="d2h-675094" class="d2h-file-wrapper" data-lang="js">\n' +
|
||||||
|
|
@ -350,32 +361,33 @@ describe("SideBySidePrinter", function() {
|
||||||
"</div>\n" +
|
"</div>\n" +
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
expect(expected).toEqual(html);
|
expect(html).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("processLines", function() {
|
describe("processLines", () => {
|
||||||
it("should process file lines", function() {
|
it("should process file lines", () => {
|
||||||
const oldLines = [
|
const oldLines: DiffLine[] = [
|
||||||
{
|
{
|
||||||
content: "-test",
|
content: "-test",
|
||||||
type: "d2h-del",
|
type: LineType.DELETE,
|
||||||
oldNumber: 1,
|
oldNumber: 1,
|
||||||
newNumber: null
|
newNumber: undefined
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const newLines = [
|
const newLines: DiffLine[] = [
|
||||||
{
|
{
|
||||||
content: "+test1r",
|
content: "+test1r",
|
||||||
type: "d2h-ins",
|
type: LineType.INSERT,
|
||||||
oldNumber: null,
|
oldNumber: undefined,
|
||||||
newNumber: 1
|
newNumber: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const sideBySidePrinter = new SideBySidePrinter({ matching: "lines" });
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const html = sideBySidePrinter.processLines(false, oldLines, newLines);
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, { matching: "lines" });
|
||||||
|
const html = sideBySideRenderer.processLines(false, oldLines, newLines);
|
||||||
const expectedLeft =
|
const expectedLeft =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||||
|
|
@ -402,8 +414,8 @@ describe("SideBySidePrinter", function() {
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr>";
|
"</tr>";
|
||||||
|
|
||||||
expect(expectedLeft).toEqual(html.left);
|
expect(html.left).toEqual(expectedLeft);
|
||||||
expect(expectedRight).toEqual(html.right);
|
expect(html.right).toEqual(expectedRight);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
const Utils = require("../utils.js").Utils;
|
import { escapeForHtml } from "../utils";
|
||||||
|
|
||||||
describe("Utils", function() {
|
describe("Utils", function() {
|
||||||
describe("escape", function() {
|
describe("escape", function() {
|
||||||
it("should escape & with &", function() {
|
it("should escape & with &", function() {
|
||||||
const result = Utils.escape("&");
|
const result = escapeForHtml("&");
|
||||||
expect("&").toEqual(result);
|
expect("&").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should escape < with <", function() {
|
it("should escape < with <", function() {
|
||||||
const result = Utils.escape("<");
|
const result = escapeForHtml("<");
|
||||||
expect("<").toEqual(result);
|
expect("<").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should escape > with >", function() {
|
it("should escape > with >", function() {
|
||||||
const result = Utils.escape(">");
|
const result = escapeForHtml(">");
|
||||||
expect(">").toEqual(result);
|
expect(">").toEqual(result);
|
||||||
});
|
});
|
||||||
it("should escape a string with multiple problematic characters", function() {
|
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>";
|
const expected = "<a href="#">\tlink text</a>";
|
||||||
expect(expected).toEqual(result);
|
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>
|
<tr>
|
||||||
<td class="{{lineClass}} {{diffParser.LINE_TYPE.INFO}}"></td>
|
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td>
|
||||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
<td class="{{CSSLineClass.INFO}}">
|
||||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">{{{blockHeader}}}</div>
|
<div class="{{contentClass}} {{CSSLineClass.INFO}}">{{{blockHeader}}}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{diffParser.LINE_TYPE.INFO}}">
|
<td class="{{CSSLineClass.INFO}}">
|
||||||
<div class="{{contentClass}} {{diffParser.LINE_TYPE.INFO}}">
|
<div class="{{contentClass}} {{CSSLineClass.INFO}}">
|
||||||
File without changes
|
File without changes
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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",
|
"target": "es5",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"lib": ["es5", "es6", "es7", "esnext", "dom", "dom.iterable"],
|
||||||
// TODO: Change to true after migration to TS is complete
|
// TODO: Change to true after migration to TS is complete
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
|
|
@ -21,8 +22,9 @@
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*"],
|
"include": ["./src/**/*", "./typings/**/*"],
|
||||||
"exclude": ["node_modules", "./src/__tests__/*"]
|
"exclude": ["node_modules", "./src/__tests__/*"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@
|
||||||
"declarationMap": false,
|
"declarationMap": false,
|
||||||
"sourceMap": 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 -->
|
<!-- diff2html -->
|
||||||
<script type="text/javascript" src="assets/diff2html.min.js"></script>
|
|
||||||
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
|
<script type="text/javascript" src="assets/diff2html-ui.min.js"></script>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
/* global Diff2HtmlUI */
|
/* global Diff2HtmlUI */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Example URLs:
|
* Example URLs:
|
||||||
|
|
@ -13,227 +14,229 @@
|
||||||
* https://bitbucket.org/atlassian/amps/pull-requests/236
|
* 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
|
// Improves browser compatibility
|
||||||
require("whatwg-fetch");
|
require("whatwg-fetch");
|
||||||
|
|
||||||
const searchParam = "diff";
|
const elements = {
|
||||||
|
root: document.getElementById("url-diff-container"),
|
||||||
const $container = $(".container");
|
container: document.getElementsByClassName("container"),
|
||||||
const $url = $("#url");
|
url: document.getElementById("url"),
|
||||||
const $outputFormat = $("#diff-url-options-output-format");
|
outputFormat: document.getElementById("diff-url-options-output-format"),
|
||||||
const $showFiles = $("#diff-url-options-show-files");
|
showFiles: document.getElementById("diff-url-options-show-files"),
|
||||||
const $matching = $("#diff-url-options-matching");
|
matching: document.getElementById("diff-url-options-matching"),
|
||||||
const $wordsThreshold = $("#diff-url-options-match-words-threshold");
|
wordsThreshold: document.getElementById("diff-url-options-match-words-threshold"),
|
||||||
const $matchingMaxComparisons = $("#diff-url-options-matching-max-comparisons");
|
matchingMaxComparisons: document.getElementById("diff-url-options-matching-max-comparisons")
|
||||||
|
};
|
||||||
|
|
||||||
if (window.location.search) {
|
if (window.location.search) {
|
||||||
const url = getUrlFromSearch(window.location.search);
|
const url = getUrlFromSearch(window.location.search);
|
||||||
$url.val(url);
|
elements.url.val(url);
|
||||||
smartDraw(url);
|
smartDraw(url, elements.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
bind();
|
bind();
|
||||||
|
|
||||||
$outputFormat
|
elements.outputFormat
|
||||||
.add($showFiles)
|
.add(elements.showFiles)
|
||||||
.add($matching)
|
.add(elements.matching)
|
||||||
.add($wordsThreshold)
|
.add(elements.wordsThreshold)
|
||||||
.add($matchingMaxComparisons)
|
.add(elements.matchingMaxComparisons)
|
||||||
.change(function(e) {
|
.change(() => smartDraw(null, elements.url, true));
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* eslint-enable @typescript-eslint/explicit-function-return-type */
|
||||||
|
|
|
||||||
227
yarn.lock
227
yarn.lock
|
|
@ -10,17 +10,17 @@
|
||||||
"@babel/highlight" "^7.0.0"
|
"@babel/highlight" "^7.0.0"
|
||||||
|
|
||||||
"@babel/core@^7.1.0":
|
"@babel/core@^7.1.0":
|
||||||
version "7.6.2"
|
version "7.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
|
||||||
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==
|
integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.5.5"
|
"@babel/code-frame" "^7.5.5"
|
||||||
"@babel/generator" "^7.6.2"
|
"@babel/generator" "^7.6.4"
|
||||||
"@babel/helpers" "^7.6.2"
|
"@babel/helpers" "^7.6.2"
|
||||||
"@babel/parser" "^7.6.2"
|
"@babel/parser" "^7.6.4"
|
||||||
"@babel/template" "^7.6.0"
|
"@babel/template" "^7.6.0"
|
||||||
"@babel/traverse" "^7.6.2"
|
"@babel/traverse" "^7.6.3"
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.3"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.1.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
json5 "^2.1.0"
|
json5 "^2.1.0"
|
||||||
|
|
@ -29,12 +29,12 @@
|
||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.0"
|
||||||
|
|
||||||
"@babel/generator@^7.4.0", "@babel/generator@^7.6.2":
|
"@babel/generator@^7.4.0", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4":
|
||||||
version "7.6.2"
|
version "7.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03"
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
|
||||||
integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==
|
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.3"
|
||||||
jsesc "^2.5.1"
|
jsesc "^2.5.1"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.0"
|
||||||
|
|
@ -85,10 +85,10 @@
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
js-tokens "^4.0.0"
|
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":
|
"@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.2"
|
version "7.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
|
||||||
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
|
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
|
||||||
|
|
||||||
"@babel/plugin-syntax-object-rest-spread@^7.0.0":
|
"@babel/plugin-syntax-object-rest-spread@^7.0.0":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
|
|
@ -106,25 +106,25 @@
|
||||||
"@babel/parser" "^7.6.0"
|
"@babel/parser" "^7.6.0"
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.0"
|
||||||
|
|
||||||
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2":
|
"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3":
|
||||||
version "7.6.2"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
|
||||||
integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==
|
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.5.5"
|
"@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-function-name" "^7.1.0"
|
||||||
"@babel/helper-split-export-declaration" "^7.4.4"
|
"@babel/helper-split-export-declaration" "^7.4.4"
|
||||||
"@babel/parser" "^7.6.2"
|
"@babel/parser" "^7.6.3"
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.3"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
lodash "^4.17.13"
|
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":
|
"@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.1"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
|
||||||
integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==
|
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
|
|
@ -332,6 +332,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.3.0"
|
"@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":
|
"@types/eslint-visitor-keys@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||||
|
|
@ -351,10 +356,10 @@
|
||||||
"@types/minimatch" "*"
|
"@types/minimatch" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/hogan.js@^3.0.0":
|
"@types/highlight.js@9.12.3":
|
||||||
version "3.0.0"
|
version "9.12.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/hogan.js/-/hogan.js-3.0.0.tgz#bf26560f39a38224ab6d0491b06f72c8fbe0953d"
|
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
|
||||||
integrity sha512-djkvb/AN43c3lIGCojNQ1FBS9VqqKhcTns5RQnHw4xBT/csy0jAssAsOiJ8NfaaioZaeKYE7XkVRxE5NeSZcaA==
|
integrity sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
|
@ -398,19 +403,24 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
"@types/mkdirp@^0.5.2":
|
"@types/mkdirp@0.5.2":
|
||||||
version "0.5.2"
|
version "0.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
|
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
|
||||||
integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==
|
integrity sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^12.7.2":
|
"@types/node@*":
|
||||||
version "12.7.11"
|
version "12.7.12"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc"
|
||||||
integrity sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==
|
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"
|
version "3.0.29"
|
||||||
resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce"
|
resolved "https://registry.yarnpkg.com/@types/nopt/-/nopt-3.0.29.tgz#f19df3db4c97ee1459a2740028320a71d70964ce"
|
||||||
integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4=
|
integrity sha1-8Z3z20yX7hRZonQAKDIKcdcJZM4=
|
||||||
|
|
@ -726,18 +736,18 @@ atob@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
autoprefixer@^9.6.0:
|
autoprefixer@9.6.0:
|
||||||
version "9.6.4"
|
version "9.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.4.tgz#e6453be47af316b2923eaeaed87860f52ad4b7eb"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
|
||||||
integrity sha512-Koz2cJU9dKOxG8P1f8uVaBntOv9lP4yz9ffWvWaicv9gHBPhpQB22nGijwd8gqW9CNT+UdkbQOQNLVI8jN1ZfQ==
|
integrity sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist "^4.7.0"
|
browserslist "^4.6.1"
|
||||||
caniuse-lite "^1.0.30000998"
|
caniuse-lite "^1.0.30000971"
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
normalize-range "^0.1.2"
|
normalize-range "^0.1.2"
|
||||||
num2fraction "^1.2.2"
|
num2fraction "^1.2.2"
|
||||||
postcss "^7.0.18"
|
postcss "^7.0.16"
|
||||||
postcss-value-parser "^4.0.2"
|
postcss-value-parser "^3.3.1"
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
aws-sign2@~0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
|
|
@ -949,7 +959,7 @@ browserify-zlib@~0.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pako "~1.0.5"
|
pako "~1.0.5"
|
||||||
|
|
||||||
browserify@^16.5.0:
|
browserify@16.5.0:
|
||||||
version "16.5.0"
|
version "16.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881"
|
resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881"
|
||||||
integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==
|
integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==
|
||||||
|
|
@ -1003,7 +1013,7 @@ browserify@^16.5.0:
|
||||||
vm-browserify "^1.0.0"
|
vm-browserify "^1.0.0"
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
browserslist@^4.7.0:
|
browserslist@^4.6.1:
|
||||||
version "4.7.0"
|
version "4.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
|
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
|
||||||
integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==
|
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"
|
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
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"
|
version "1.0.30000999"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
|
||||||
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
|
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
|
||||||
|
|
@ -1186,7 +1196,7 @@ class-utils@^0.3.5:
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
clean-css-cli@^4.3.0:
|
clean-css-cli@4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/clean-css-cli/-/clean-css-cli-4.3.0.tgz#8502aa86d1879e5b111af51b3c2abb799e0684ce"
|
resolved "https://registry.yarnpkg.com/clean-css-cli/-/clean-css-cli-4.3.0.tgz#8502aa86d1879e5b111af51b3c2abb799e0684ce"
|
||||||
integrity sha512-8GHZfr+mG3zB/Lgqrr27qHBFsPSn0fyEI3f2rIZpxPxUbn2J6A8xyyeBRVTW8duDuXigN0s80vsXiXJOEFIO5Q==
|
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"
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
|
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
|
||||||
|
|
||||||
codacy-coverage@^3.4.0:
|
codacy-coverage@3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-3.4.0.tgz#196af70844c4e4179718f7a7f9d96b921b4b3a67"
|
resolved "https://registry.yarnpkg.com/codacy-coverage/-/codacy-coverage-3.4.0.tgz#196af70844c4e4179718f7a7f9d96b921b4b3a67"
|
||||||
integrity sha512-A0ats3/gZtOw76muu++HZ6QrInztWjjLefkLJmmBpjPfyn6nNwNLoApmGmj3F3dfgl2+o6u5GwPnUBkKdfKXTQ==
|
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"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||||
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
|
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
|
||||||
|
|
||||||
commander@2.x, commander@^2.20.0, commander@^2.x, commander@~2.20.0:
|
commander@2.20.0:
|
||||||
version "2.20.1"
|
version "2.20.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
|
||||||
integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==
|
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:
|
component-emitter@^1.2.1:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
|
|
@ -1619,7 +1634,7 @@ diff@3.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||||
|
|
||||||
diff@^4.0.1:
|
diff@4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
||||||
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
|
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
|
||||||
|
|
@ -1683,9 +1698,9 @@ ecc-jsbn@~0.1.1:
|
||||||
safer-buffer "^2.1.0"
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
electron-to-chromium@^1.3.247:
|
electron-to-chromium@^1.3.247:
|
||||||
version "1.3.275"
|
version "1.3.281"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.275.tgz#19a38436e34216f51820fa2f4326d5ce141fa36f"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.281.tgz#2dbeb9f0bdffddb1662f9ca00d26c49d31dc0f7e"
|
||||||
integrity sha512-/YWtW/VapMnuYA1lNOaa1F4GhR1LBf+CUTp60lzDPEEh0XOzyOAyULyYZVF9vziZ3qSbTqCwmKwsyRXp66STbw==
|
integrity sha512-oxXKngPjTWRmXFy4vV9FeAkPl7wU4xMejfOY+HXjGrj4T0z9l96loWWVDLJEtbT/aPKOWKrSz6xoYxd+YJ/gJA==
|
||||||
|
|
||||||
elliptic@^6.0.0:
|
elliptic@^6.0.0:
|
||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
|
|
@ -1845,12 +1860,12 @@ eslint-plugin-prettier@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier-linter-helpers "^1.0.0"
|
prettier-linter-helpers "^1.0.0"
|
||||||
|
|
||||||
eslint-plugin-promise@^4.2.1:
|
eslint-plugin-promise@4.2.1:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
|
||||||
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
|
||||||
|
|
||||||
eslint-plugin-standard@^4.0.1:
|
eslint-plugin-standard@4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
|
||||||
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
|
integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
|
||||||
|
|
@ -2105,7 +2120,7 @@ fast-glob@^2.2.6:
|
||||||
merge2 "^1.2.3"
|
merge2 "^1.2.3"
|
||||||
micromatch "^3.1.10"
|
micromatch "^3.1.10"
|
||||||
|
|
||||||
fast-html-parser@^1.0.1:
|
fast-html-parser@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/fast-html-parser/-/fast-html-parser-1.0.1.tgz#4ecc9683b8bb79afe11a50807b7853e79256cea2"
|
resolved "https://registry.yarnpkg.com/fast-html-parser/-/fast-html-parser-1.0.1.tgz#4ecc9683b8bb79afe11a50807b7853e79256cea2"
|
||||||
integrity sha1-TsyWg7i7ea/hGlCAe3hT55JWzqI=
|
integrity sha1-TsyWg7i7ea/hGlCAe3hT55JWzqI=
|
||||||
|
|
@ -2379,9 +2394,9 @@ growly@^1.3.0:
|
||||||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
||||||
|
|
||||||
handlebars@^4.1.2:
|
handlebars@^4.1.2:
|
||||||
version "4.4.2"
|
version "4.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.2.tgz#8810a9821a9d6d52cb2f57d326d6ce7c3dfe741d"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.3.tgz#180bae52c1d0e9ec0c15d7e82a4362d662762f6e"
|
||||||
integrity sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==
|
integrity sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==
|
||||||
dependencies:
|
dependencies:
|
||||||
neo-async "^2.6.0"
|
neo-async "^2.6.0"
|
||||||
optimist "^0.6.1"
|
optimist "^0.6.1"
|
||||||
|
|
@ -2476,6 +2491,11 @@ he@1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||||
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
|
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:
|
hmac-drbg@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
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"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c"
|
||||||
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
|
integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==
|
||||||
|
|
||||||
hogan.js@^3.0.2:
|
hogan.js@3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
||||||
integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
|
integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
|
||||||
|
|
@ -2504,9 +2524,9 @@ hogan.js@^3.0.2:
|
||||||
nopt "1.0.10"
|
nopt "1.0.10"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.4"
|
version "2.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.4.tgz#44119abaf4bc64692a16ace34700fed9c03e2546"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
|
||||||
integrity sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==
|
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
|
||||||
|
|
||||||
html-encoding-sniffer@^1.0.2:
|
html-encoding-sniffer@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
|
|
@ -2547,9 +2567,9 @@ ieee754@^1.1.4:
|
||||||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||||
|
|
||||||
ignore-walk@^3.0.1:
|
ignore-walk@^3.0.1:
|
||||||
version "3.0.2"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b"
|
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
|
||||||
integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw==
|
integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
|
|
||||||
|
|
@ -3548,6 +3568,11 @@ locate-path@^3.0.0:
|
||||||
p-locate "^3.0.0"
|
p-locate "^3.0.0"
|
||||||
path-exists "^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:
|
lodash.memoize@~3.0.3:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
|
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"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
|
||||||
integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
|
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:
|
micromatch@^3.1.10, micromatch@^3.1.4:
|
||||||
version "3.1.10"
|
version "3.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
|
||||||
|
|
@ -3912,9 +3932,9 @@ node-pre-gyp@^0.12.0:
|
||||||
tar "^4"
|
tar "^4"
|
||||||
|
|
||||||
node-releases@^1.1.29:
|
node-releases@^1.1.29:
|
||||||
version "1.1.34"
|
version "1.1.35"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.34.tgz#ced4655ee1ba9c3a2c5dcbac385e19434155fd40"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.35.tgz#32a74a3cd497aa77f23d509f483475fd160e4c48"
|
||||||
integrity sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA==
|
integrity sha512-JGcM/wndCN/2elJlU0IGdVEJQQnJwsLbgPCFd2pY7V0mxf17bZ0Gb/lgOtL29ZQhvEX5shnVhxQyZz3ex94N8w==
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^6.3.0"
|
semver "^6.3.0"
|
||||||
|
|
||||||
|
|
@ -3925,7 +3945,7 @@ nopt@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
abbrev "1"
|
abbrev "1"
|
||||||
|
|
||||||
nopt@^4.0.1:
|
nopt@4.0.1, nopt@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
||||||
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
|
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
|
||||||
|
|
@ -3966,9 +3986,9 @@ npm-bundled@^1.0.1:
|
||||||
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
|
integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
|
||||||
|
|
||||||
npm-packlist@^1.1.6:
|
npm-packlist@^1.1.6:
|
||||||
version "1.4.4"
|
version "1.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
|
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4"
|
||||||
integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
|
integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ignore-walk "^3.0.1"
|
ignore-walk "^3.0.1"
|
||||||
npm-bundled "^1.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"
|
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||||
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
||||||
|
|
||||||
postcss-cli@^6.1.3:
|
postcss-cli@6.1.3:
|
||||||
version "6.1.3"
|
version "6.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-6.1.3.tgz#a9eec3e9cde4aaa90170546baf706f8af6f8ecec"
|
resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-6.1.3.tgz#a9eec3e9cde4aaa90170546baf706f8af6f8ecec"
|
||||||
integrity sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw==
|
integrity sha512-eieqJU+OR1OFc/lQqMsDmROTJpoMZFvoAQ+82utBQ8/8qGMTfH9bBSPsTdsagYA8uvNzxHw2I2cNSSJkLAGhvw==
|
||||||
|
|
@ -4400,12 +4420,12 @@ postcss-reporter@^6.0.0:
|
||||||
log-symbols "^2.2.0"
|
log-symbols "^2.2.0"
|
||||||
postcss "^7.0.7"
|
postcss "^7.0.7"
|
||||||
|
|
||||||
postcss-value-parser@^4.0.2:
|
postcss-value-parser@^3.3.1:
|
||||||
version "4.0.2"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
|
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
|
||||||
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
|
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"
|
version "7.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
|
||||||
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
|
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
|
||||||
|
|
@ -5299,7 +5319,7 @@ tar@^4:
|
||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
yallist "^3.0.3"
|
yallist "^3.0.3"
|
||||||
|
|
||||||
terser@^4.3.8:
|
terser@4.3.8:
|
||||||
version "4.3.8"
|
version "4.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136"
|
resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.8.tgz#707f05f3f4c1c70c840e626addfdb1c158a17136"
|
||||||
integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==
|
integrity sha512-otmIRlRVmLChAWsnSFNO0Bfk6YySuBp6G9qrHiJwlLDd4mxe2ta4sjI7TzIR+W1nBMjilzrMcPOz9pSusgx3hQ==
|
||||||
|
|
@ -5420,15 +5440,16 @@ tr46@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
ts-jest@24.0.2:
|
ts-jest@24.1.0:
|
||||||
version "24.0.2"
|
version "24.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.2.tgz#8dde6cece97c31c03e80e474c749753ffd27194d"
|
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.1.0.tgz#2eaa813271a2987b7e6c3fefbda196301c131734"
|
||||||
integrity sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==
|
integrity sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
bs-logger "0.x"
|
bs-logger "0.x"
|
||||||
buffer-from "1.x"
|
buffer-from "1.x"
|
||||||
fast-json-stable-stringify "2.x"
|
fast-json-stable-stringify "2.x"
|
||||||
json5 "2.x"
|
json5 "2.x"
|
||||||
|
lodash.memoize "4.x"
|
||||||
make-error "1.x"
|
make-error "1.x"
|
||||||
mkdirp "0.x"
|
mkdirp "0.x"
|
||||||
resolve "1.x"
|
resolve "1.x"
|
||||||
|
|
@ -5476,17 +5497,17 @@ typedarray@^0.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
typescript@^3.6.3:
|
typescript@3.6.4:
|
||||||
version "3.6.3"
|
version "3.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
|
||||||
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
|
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
|
||||||
|
|
||||||
uglify-js@^3.1.4:
|
uglify-js@^3.1.4:
|
||||||
version "3.6.0"
|
version "3.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.1.tgz#ae7688c50e1bdcf2f70a0e162410003cf9798311"
|
||||||
integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
|
integrity sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "~2.20.0"
|
commander "2.20.0"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
umd@^3.0.0:
|
umd@^3.0.0:
|
||||||
|
|
@ -5643,7 +5664,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
|
|
||||||
whatwg-fetch@^3.0.0:
|
whatwg-fetch@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
|
||||||
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue