/* * * Diff to HTML (diff2html.js) * Author: rtfpessoa * Date: Friday 29 August 2014 * Last Update: Sunday 14 September 2014 * * Diff command: * git diff --word-diff-regex=. HEAD~1 */ (function (window) { var ClassVariable; ClassVariable = (function () { var LINE_TYPE = { INSERTS: "d2h-ins", ALL_NEW: "d2h-all-ins", DELETES: "d2h-del", ALL_DELETED: "d2h-all-del", INSERTS_AND_DELETES: "d2h-ins-and-del", CONTEXT: "d2h-cntx", INFO: "d2h-info" }; function Diff2Html() { } /* * Generates pretty html from string diff input */ Diff2Html.prototype.getPrettyHtmlFromDiff = function (diffInput) { var diffJson = generateDiffJson(diffInput); return generateJsonHtml(diffJson); }; /* * Generates json object from string diff input */ Diff2Html.prototype.getJsonFromDiff = function (diffInput) { return generateDiffJson(diffInput); }; /* * Generates pretty html from a json object */ Diff2Html.prototype.getPrettyHtmlFromJson = function (diffJson) { return generateJsonHtml(diffJson); }; /* * Generates pretty side by side html from string diff input */ Diff2Html.prototype.getPrettySideBySideHtmlFromDiff = function (diffInput) { var diffJson = generateDiffJson(diffInput); return generateSideBySideJsonHtml(diffJson); }; /* * Generates pretty side by side html from a json object */ Diff2Html.prototype.getPrettySideBySideHtmlFromJson = function (diffJson) { return generateSideBySideJsonHtml(diffJson); }; var generateDiffJson = function (diffInput) { var files = [], currentFile = null, currentBlock = null, oldLine = null, newLine = null; var saveBlock = function () { /* add previous block(if exists) before start a new file */ if (currentBlock) { currentFile.blocks.push(currentBlock); currentBlock = null; } }; var saveFile = function () { /* add previous file(if exists) before start a new one */ if (currentFile) { files.push(currentFile); currentFile = null; } }; var startFile = function (line) { saveBlock(); saveFile(); /* create file structure */ currentFile = {}; currentFile.blocks = []; currentFile.deletedLines = 0; currentFile.addedLines = 0; /* save file paths, before and after the diff */ var values = /^diff --git a\/(\S+) b\/(\S+).*$/.exec(line); currentFile.oldName = values[1]; currentFile.newName = values[2]; }; var startBlock = function (line) { saveBlock(); var values; if (values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line)) { oldLine = values[2]; newLine = values[4]; } else { oldLine = 0; newLine = 0; } /* create block metadata */ currentBlock = {}; currentBlock.lines = []; currentBlock.oldStartLine = oldLine; currentBlock.newStartLine = newLine; currentBlock.header = line; }; var createLine = function (line) { /* Line Types */ var isLineWithInserts = /{\+.*?\+}/.exec(line); var isNewLine = /^{\+.*?\+}$/.exec(line); var isLineWithDeletes = /\[-.*?-\]/.exec(line); var isRemovedLine = /^\[-.*?-\]$/.exec(line); var isLineWithBoth = isLineWithInserts && isLineWithDeletes; var isContextLine = !isLineWithInserts && !isLineWithDeletes; var currentLine = {}; currentLine.content = line; /* fill the line data */ if (isLineWithBoth) { currentFile.deletedLines++; currentFile.addedLines++; currentLine.type = LINE_TYPE.INSERTS_AND_DELETES; currentLine.oldNumber = oldLine++; currentLine.newNumber = newLine++; currentBlock.lines.push(currentLine); } else if (isContextLine) { currentLine.type = LINE_TYPE.CONTEXT; currentLine.oldNumber = oldLine++; currentLine.newNumber = newLine++; currentBlock.lines.push(currentLine); } else if (isNewLine) { currentFile.addedLines++; currentLine.type = LINE_TYPE.ALL_NEW; currentLine.oldNumber = null; currentLine.newNumber = newLine++; currentBlock.lines.push(currentLine); } else if (isRemovedLine) { currentFile.deletedLines++; currentLine.type = LINE_TYPE.ALL_DELETED; currentLine.oldNumber = oldLine++; currentLine.newNumber = null; currentBlock.lines.push(currentLine); } else if (isLineWithInserts) { currentFile.addedLines++; currentLine.type = LINE_TYPE.INSERTS; currentLine.oldNumber = oldLine++; currentLine.newNumber = newLine++; currentBlock.lines.push(currentLine); } else if (isLineWithDeletes) { currentFile.deletedLines++; currentLine.type = LINE_TYPE.DELETES; currentLine.oldNumber = oldLine++; currentLine.newNumber = newLine++; currentBlock.lines.push(currentLine); } }; var diffLines = diffInput.split("\n"); diffLines.forEach(function (line) { // Unmerged paths, and possibly other non-diffable files // https://github.com/scottgonzalez/pretty-diff/issues/11 // Also, remove some useless lines if (!line || startsWith(line, "*") || startsWith(line, "new") || startsWith(line, "index") || startsWith(line, "---") || startsWith(line, "+++")) { return; } if (startsWith(line, "diff")) { startFile(line); } else if (currentFile && startsWith(line, "@@")) { startBlock(line); } else if (currentBlock) { createLine(line); } }); saveBlock(); saveFile(); return files; }; /* * Line By Line HTML */ var generateJsonHtml = function (diffFiles) { return "
\n" + diffFiles.map(function (file) { return "
\n" + "
\n" + "
\n" + " +" + file.addedLines + "\n" + " -" + file.deletedLines + "\n" + "
\n" + "
" + getDiffName(file.oldName, file.newName) + "
\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " " + generateFileHtml(file) + " \n" + "
\n" + "
\n" + "
\n" + "
\n"; }).join("\n") + "
\n"; }; var generateFileHtml = function (file) { return file.blocks.map(function (block) { return "\n" + " \n" + " " + "
" + escape(block.header) + "
" + " \n" + "\n" + block.lines.map(function (line) { var newLine = function () { var lineData = {}; lineData.oldLine = valueOrEmpty(line.oldNumber); lineData.newLine = valueOrEmpty(line.newNumber); return lineData; }; var escapedLine = escape(line.content); var lines = []; var lineData = {}; switch (line.type) { case LINE_TYPE.INSERTS: case LINE_TYPE.ALL_NEW: lineData = newLine(); lineData.content = generateLineInsertions(escapedLine); lineData.type = LINE_TYPE.INSERTS; lines.push(lineData); break; case LINE_TYPE.DELETES: case LINE_TYPE.ALL_DELETED: lineData = newLine(); lineData.content = generateLineDeletions(escapedLine); lineData.type = LINE_TYPE.DELETES; lines.push(lineData); break; case LINE_TYPE.INSERTS_AND_DELETES: lineData = newLine(); lineData.content = generateLineDeletions(escapedLine); lineData.type = LINE_TYPE.DELETES; lines.push(lineData); lineData = newLine(); lineData.content = generateLineInsertions(escapedLine); lineData.type = LINE_TYPE.INSERTS; lines.push(lineData); break; default: lineData = newLine(); lineData.content = escapedLine; lineData.type = LINE_TYPE.CONTEXT; lines.push(lineData); break; } return lines.map(generateLineHtml).join("\n"); }).join("\n"); }).join("\n"); }; var generateLineHtml = function (line) { return "\n" + " " + "
" + line.oldLine + "
" + "
" + line.newLine + "
" + " \n" + " " + "
" + line.content + "
" + " \n" + "\n"; }; /* * Side By Side HTML (work in progress) */ var generateSideBySideJsonHtml = function (diffFiles) { return "
\n" + diffFiles.map(function (file) { return "
\n" + "
\n" + "
\n" + " +" + file.addedLines + "\n" + " -" + file.deletedLines + "\n" + "
\n" + "
" + getDiffName(file.oldName, file.newName) + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " " + generateLeftSideFileHtml(file) + " \n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " " + generateRightSideFileHtml(file) + " \n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"; }).join("\n") + "
\n"; }; var generateLeftSideFileHtml = function (file) { return file.blocks.map(function (block) { return "\n" + " \n" + " " + "
" + escape(block.header) + "
" + " \n" + "\n" + block.lines.map(function (line) { var emptyLine = function () { var lineData = {}; lineData.number = ""; lineData.content = ""; lineData.type = LINE_TYPE.CONTEXT; return lineData; }; var escapedLine = escape(line.content); var lines = []; var lineData = {}; switch (line.type) { case LINE_TYPE.INSERTS: lineData = {}; lineData.number = valueOrEmpty(line.oldNumber); lineData.content = removeInserts(escapedLine); lineData.type = LINE_TYPE.CONTEXT; lines.push(lineData); break; case LINE_TYPE.ALL_NEW: lines.push(new emptyLine()); break; case LINE_TYPE.DELETES: lineData = {}; lineData.number = valueOrEmpty(line.oldNumber); lineData.content = generateLineDeletions(escapedLine); lineData.type = LINE_TYPE.DELETES; lines.push(lineData); break; case LINE_TYPE.ALL_DELETED: lineData = {}; lineData.number = valueOrEmpty(line.oldNumber); lineData.content = generateLineDeletions(escapedLine); lineData.type = LINE_TYPE.DELETES; lines.push(lineData); break; case LINE_TYPE.INSERTS_AND_DELETES: lineData = {}; lineData.number = valueOrEmpty(line.oldNumber); lineData.content = generateLineDeletions(escapedLine); lineData.type = LINE_TYPE.DELETES; lines.push(lineData); break; default: lineData = {}; lineData.number = valueOrEmpty(line.oldNumber); lineData.content = escapedLine; lineData.type = LINE_TYPE.CONTEXT; lines.push(lineData); break; } return "\n" + lines.map(generateSingleLineHtml).join("\n") + "\n"; }).join("\n"); }).join("\n"); }; var generateRightSideFileHtml = function (file) { return file.blocks.map(function (block) { return "\n" + " \n" + " " + "
" + " \n" + "\n" + block.lines.map(function (line) { var emptyLine = function () { var lineData = {}; lineData.number = ""; lineData.content = ""; lineData.type = LINE_TYPE.CONTEXT; return lineData; }; var escapedLine = escape(line.content); var lines = []; var lineData = {}; switch (line.type) { case LINE_TYPE.INSERTS: lineData = {}; lineData.number = valueOrEmpty(line.newNumber); lineData.content = generateLineInsertions(escapedLine); lineData.type = LINE_TYPE.INSERTS; lines.push(lineData); break; case LINE_TYPE.ALL_NEW: lineData = {}; lineData.number = valueOrEmpty(line.newNumber); lineData.content = generateLineInsertions(escapedLine); lineData.type = LINE_TYPE.INSERTS; lines.push(lineData); break; case LINE_TYPE.DELETES: lineData = {}; lineData.number = valueOrEmpty(line.newNumber); lineData.content = removeDeletes(escapedLine); lineData.type = LINE_TYPE.CONTEXT; lines.push(lineData); break; case LINE_TYPE.ALL_DELETED: lines.push(new emptyLine()); break; case LINE_TYPE.INSERTS_AND_DELETES: lineData = {}; lineData.number = valueOrEmpty(line.newNumber); lineData.content = generateLineInsertions(escapedLine); lineData.type = LINE_TYPE.INSERTS; lines.push(lineData); break; default: lineData = {}; lineData.number = valueOrEmpty(line.newNumber); lineData.content = escapedLine; lineData.type = LINE_TYPE.CONTEXT; lines.push(lineData); break; } return "\n" + lines.map(generateSingleLineHtml).join("\n") + "\n"; }).join("\n"); }).join("\n"); }; var generateSingleLineHtml = function (line) { return "" + line.number + "\n" + " " + "
" + line.content + "
" + " \n"; }; /* * HTML Helpers */ var getDiffName = function (oldFilename, newFilename) { return oldFilename === newFilename ? newFilename : oldFilename + " -> " + newFilename; }; var generateLineInsertions = function (line) { return line.slice(0).replace(/(\[-.*?-\])/g, ""). replace(/({\+(.*?)\+})/g, "$2"); }; var generateLineDeletions = function (line) { return line.slice(0).replace(/({\+.*?\+})/g, ""). replace(/(\[-(.*?)-\])/g, "$2"); }; var removeDeletes = function (line) { return line.slice(0).replace(/({\+.*?\+})/g, ""). replace(/(\[-.*?-\])/g, ""); }; var removeInserts = function (line) { return line.slice(0).replace(/({\+.*?\+})/g, ""). replace(/(\[-.*?-\])/g, ""); }; /* * Utils */ function escape(str) { return str.slice(0) .replace(/&/g, "&") .replace(//g, ">") .replace(/\t/g, " "); } function startsWith(str, start) { return str.indexOf(start) === 0; } function valueOrEmpty(value) { return value ? value : ""; } /* singleton pattern */ var instance; return { getInstance: function () { if (instance === undefined) { instance = new Diff2Html(); /* Hide the constructor so the returned objected can't be new'd */ instance.constructor = null; } return instance; } }; })(); window.Diff2Html = ClassVariable.getInstance(); return window.Diff2Html; })(window);