From cc9d271cc0d52b5de2c96917b0ffc2acdbed417b Mon Sep 17 00:00:00 2001 From: Rodrigo Fernandes Date: Sat, 30 Aug 2014 23:16:38 +0100 Subject: [PATCH] char by char diff & clean a bit --- .gitignore | 16 ++ diff2html.js | 474 ++++++++++++++++++++++++-------------------- style.css | 215 ++++++++++---------- template.html | 236 +++++----------------- word-diff-parser.js | 136 ------------- 5 files changed, 426 insertions(+), 651 deletions(-) create mode 100644 .gitignore delete mode 100644 word-diff-parser.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc368f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Eclipse +.classpath +.project +.settings/ + +# Intellij +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +log/ +target/ diff --git a/diff2html.js b/diff2html.js index 8fd76db..bc6fc69 100644 --- a/diff2html.js +++ b/diff2html.js @@ -3,254 +3,290 @@ * Diff to HTML (diff2html.js) * Author: rtfpessoa * Date: Friday 29 August 2014 + * Last Update: Saturday 30 August 2014 * - * Useful commands: - * git diff HEAD~1 - * git diff HEAD~1 --word-diff-regex='[[:alnum:]]+|[^[:space:]]' - * git diff HEAD~1 --word-diff-regex=. + * Diff command: + * git diff --word-diff-regex=. HEAD~1 */ -(function($, window) { - var ClassVariable; +(function (window) { + var ClassVariable; - ClassVariable = (function() { + ClassVariable = (function () { - var CSS_STYLES = { - INFO: "info", - CONTEXT: "context", - NEW: "insert", - DELETED: "delete" - }; + var CSS_STYLES = { + INFO: "info", + CONTEXT: "context", + NEW: "insert", + DELETED: "delete" + }; - var BLOCK_HEADER_LINE = "..."; + var BLOCK_HEADER_LINE = "..."; - var wordDiffParser = WordDiffParser.getInstance(); - - function Diff2Html() {} - - Diff2Html.prototype.generatePrettyDiff = function(diffInput, wordDiffInput) { - var diffFiles = splitByFile(diffInput); - var changedWords = wordDiffParser.generateChangedWords(wordDiffInput); - - if (!changedWords) { - changedWords = {}; - } - - var html = generateHtml(diffFiles, changedWords); - - return html; - }; - - var splitByFile = function(diffInput) { - var files = [], - currentFile = null, - currentBlock = null, - oldLine = null, - newLine = null; - - diffInput.split("\n").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 || line.charAt(0) === "*" || - line.indexOf("new") === 0 || - line.indexOf("index") === 0 || - line.indexOf("---") === 0 || - line.indexOf("+++") === 0) { - return; + function Diff2Html() { } - if (line.indexOf("diff") === 0) { - /* File Diff Line */ + /* + * Generates pretty html from string diff input + */ + Diff2Html.prototype.getPrettyHtmlFromDiff = function (diffInput) { + var diffJson = generateDiffJson(diffInput); + return generateJsonHtml(diffJson); + }; - /* add previous block(if exists) before start a new file */ - if (currentBlock) { - currentFile.blocks.push(currentBlock); - currentBlock = null; - } + /* + * Generates json object from string diff input + */ + Diff2Html.prototype.getJsonFromDiff = function (diffInput) { + return generateDiffJson(diffInput); + }; - /* add previous file(if exists) before start a new one */ - if (currentFile) { - files.push(currentFile); - currentFile = null; - } + /* + * Generates pretty html from a json object + */ + Diff2Html.prototype.getPrettyHtmlFromJson = function (diffJson) { + return generateJsonHtml(diffJson); + }; - /* create file structure */ - currentFile = {}; - currentFile.blocks = []; - currentFile.deletedLines = 0, - currentFile.addedLines = 0; + var generateDiffJson = function (diffInput) { + var files = [], + currentFile = null, + currentBlock = null, + oldLine = null, + newLine = null; - /* 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 saveBlock = function () { + /* add previous block(if exists) before start a new file */ + if (currentBlock) { + currentFile.blocks.push(currentBlock); + currentBlock = null; + } + }; - } else if (line.indexOf("@@") === 0) { - /* Diff Block Header Line */ + var saveFile = function () { + /* add previous file(if exists) before start a new one */ + if (currentFile) { + files.push(currentFile); + currentFile = null; + } + }; - var values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line); + var startFile = function (line) { + saveBlock(); + saveFile(); - /* add previous block(if exists) before start a new one */ - if (currentBlock) { - currentFile.blocks.push(currentBlock); - currentBlock = null; - } + /* create file structure */ + currentFile = {}; + currentFile.blocks = []; + currentFile.deletedLines = 0; + currentFile.addedLines = 0; - /* create block metadata */ - currentBlock = {}; - currentBlock.lines = []; - currentBlock.oldStartLine = oldLine = values[2]; - currentBlock.newStartLine = newLine = values[4]; - /* update file added and deleted lines */ - currentFile.deletedLines += currentBlock.deletedLines = parseInt(values[3], 10); - currentFile.addedLines += currentBlock.addedLines = parseInt(values[5], 10); + /* 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]; + }; - /* create block header line */ - var currentLine = {}; - currentLine.type = CSS_STYLES.INFO; - currentLine.content = line; - currentLine.oldNumber = BLOCK_HEADER_LINE; - currentLine.newNumber = BLOCK_HEADER_LINE; + var startBlock = function (line) { + saveBlock(); - /* add line to block */ - currentBlock.lines.push(currentLine); + var values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line); - } else { - /* Regular Diff Line */ + /* create block metadata */ + currentBlock = {}; + currentBlock.lines = []; + currentBlock.oldStartLine = oldLine = values[2]; + currentBlock.newStartLine = newLine = values[4]; - var currentLine = {}; - currentLine.content = line; + /* create block header line */ + var currentLine = {}; + currentLine.type = CSS_STYLES.INFO; + currentLine.content = line; + currentLine.oldNumber = BLOCK_HEADER_LINE; + currentLine.newNumber = BLOCK_HEADER_LINE; - if (line.indexOf("+") === 0) { - currentLine.type = CSS_STYLES.NEW; - currentLine.oldNumber = null; - currentLine.newNumber = newLine++; + /* add line to block */ + currentBlock.lines.push(currentLine); + }; - } else if (line.indexOf("-") === 0) { - currentLine.type = CSS_STYLES.DELETED; - currentLine.oldNumber = oldLine++; - currentLine.newNumber = null; + var createLine = function (line) { + var isLineWithInserts = /{\+.*?\+}/.exec(line); + var isLineWithDeletes = /\[-.*?-\]/.exec(line); + var isNewLine = /^{\+.*?\+}$/.exec(line); + var isContextLine = !isLineWithInserts && !isLineWithDeletes; - } else { - currentLine.type = CSS_STYLES.CONTEXT; - currentLine.oldNumber = oldLine++; - currentLine.newNumber = newLine++; + var currentLine = {}; - } + if (isContextLine) { + currentLine = {}; + currentLine.type = CSS_STYLES.CONTEXT; + currentLine.oldNumber = oldLine++; + currentLine.newNumber = newLine++; + currentLine.content = line; - /* add line to block */ - currentBlock.lines.push(currentLine); + currentBlock.lines.push(currentLine); + } else { + + if (isLineWithDeletes) { + currentFile.deletedLines++; + + currentLine = {}; + currentLine.type = CSS_STYLES.DELETED; + currentLine.oldNumber = oldLine++; + currentLine.newNumber = null; + currentLine.content = line; + + currentBlock.lines.push(currentLine); + + } + + if (isLineWithInserts) { + currentFile.addedLines++; + + currentLine = {}; + currentLine.type = CSS_STYLES.NEW; + currentLine.oldNumber = null; + currentLine.newNumber = newLine++; + currentLine.content = line; + + /* fix line numbers when new chars but no deletes and no whole new line */ + if (isLineWithInserts && !isLineWithDeletes && !isNewLine) { + currentFile.deletedLines++; + + currentLine.oldNumber = oldLine++; + } + + 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; + }; + + 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 getDiffName = function (oldFilename, newFilename) { + return oldFilename === newFilename ? newFilename : oldFilename + " -> " + newFilename; + }; + + var generateFileHtml = function (file) { + return file.blocks.map(function (block) { + return block.lines.map(function (line) { + + var oldLine = valueOrEmpty(line.oldNumber); + var newLine = valueOrEmpty(line.newNumber); + + var escapedLine = escape(line.content); + + if (line.type === CSS_STYLES.NEW) { + escapedLine = generateLineInsertions(escapedLine); + } else if (line.type === CSS_STYLES.DELETED) { + escapedLine = generateLineDeletions(escapedLine); + } + + return "\n" + + " " + oldLine + "\n" + + " " + newLine + "\n" + + "
" + escapedLine + "
\n" + + "\n"; + }).join("\n"); + }).join("\n"); + }; + + var generateLineInsertions = function (line) { + return line.replace(/(\[-.*?-\])/g, ""). + replace(/({\+(.*?)\+})/g, "$2"); + }; + + var generateLineDeletions = function (line) { + return line.replace(/({\+.*?\+})/g, ""). + replace(/(\[-(.*?)-\])/g, "$2"); + }; + + /* + * Utils + */ + + function escape(str) { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\t/g, " "); } - }); - /* add previous block(if exists) before start a new file */ - if (currentBlock) { - currentFile.blocks.push(currentBlock); - currentBlock = null; - } - - /* add previous file(if exists) before start a new one */ - if (currentFile) { - files.push(currentFile); - currentFile = null; - } - - return files; - }; - - var generateHtml = function(diffFiles, changedWords) { - return diffFiles.map(function(file, index) { - var fileHeader = file.oldName === file.newName ? file.newName : file.oldName + " -> " + file.newName - - return "
" + - "
" + - "
" + - " +" + file.addedLines + "" + - " -" + file.deletedLines + "" + - "
" + - "
" + fileHeader + "
" + - "
" + - "
" + - "
" + - " " + - " " + - generateFileHtml(file, changedWords[index]) + - " " + - "
" + - "
" + - "
" + - "
"; - }); - }; - - var generateFileHtml = function(file, changedWords) { - return file.blocks.map(function(block) { - return block.lines.map(function(line) { - - var oldLine = line.oldNumber ? line.oldNumber : ""; - var newLine = line.newNumber ? line.newNumber : ""; - - var oldWords = []; - var newWords = []; - - if (oldLine && oldLine !== BLOCK_HEADER_LINE && - changedWords && changedWords.deletedWords && changedWords.deletedWords[oldLine]) { - oldWords = changedWords.deletedWords[oldLine]; - } - - if (newLine && newLine !== BLOCK_HEADER_LINE && - changedWords && changedWords.addedWords && changedWords.addedWords[newLine]) { - newWords = changedWords.addedWords[newLine]; - } - - //var newLine = escape(line.content); - var newCodeLine = line.content; - newCodeLine = markWords(oldWords, newCodeLine, "del"); - newCodeLine = markWords(newWords, newCodeLine, "ins"); - - return "" + - " " + oldLine + "" + - " " + newLine + "" + - "
" + newCodeLine + "
" + - ""; - }).join("\n"); - }).join("\n"); - }; - - var markWords = function(words, line, clazz) { - var newLine = line; - words.forEach(function(word) { - newLine = newLine.replace(word, "<" + clazz + ">" + word + ""); - }); - - return newLine; - }; - - var escape = function(str) { - return str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\t/g, " "); - }; - - /* 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; + function startsWith(str, start) { + return str.indexOf(start) === 0; } - return instance; - } - }; - })(); + function valueOrEmpty(value) { + return value ? value : ""; + } - window.Diff2Html = ClassVariable; - return window.Diff2Html; + /* 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; + } + }; -})(jQuery, window); + })(); + + window.Diff2Html = ClassVariable.getInstance(); + return window.Diff2Html; + +})(window); diff --git a/style.css b/style.css index 33e2eb3..b52382e 100644 --- a/style.css +++ b/style.css @@ -3,186 +3,183 @@ * Diff to HTML (style.css) * Author: rtfpessoa * Date: Friday 29 August 2014 + * Last Update: Saturday 30 August 2014 * */ body { - text-align: center; + text-align: center; } #wrapper { - display: inline-block; - margin-top: 1em; - text-align: left; - width: 920px; + display: inline-block; + margin-top: 1em; + text-align: left; + width: 920px; } .file-wrapper { - border: 1px solid #ddd; - border-radius: 3px; - margin-bottom: 1em; + border: 1px solid #ddd; + border-radius: 3px; + margin-bottom: 1em; } .file-header { - padding: 5px 10px; - text-shadow: 0 1px 0 #fff; - border-bottom: 1px solid #d8d8d8; - background-color: #f7f7f7; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - box-sizing: border-box; + padding: 5px 10px; + text-shadow: 0 1px 0 #fff; + border-bottom: 1px solid #d8d8d8; + background-color: #f7f7f7; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + box-sizing: border-box; } .file-name { - display: inline-block; - font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; - line-height: 1.4; - height: 33px; - line-height: 33px; + display: inline-block; + font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; + height: 33px; + line-height: 33px; } .file-stats { - display: inline-block; - font-family: monospace,monospace; - font-size: 12px; - vertical-align: middle; + display: inline-block; + font-family: monospace, monospace; + font-size: 12px; + vertical-align: middle; } .lines-added { - background-color: #ceffce; - border-color: #b4e2b4; - border: 1px solid; - color: #399839; - border-radius: 5px 0 0 5px; - padding: 5px; + background-color: #ceffce; + border: 1px solid #b4e2b4; + color: #399839; + border-radius: 5px 0 0 5px; + padding: 5px; } .lines-deleted { - background-color: #f7c8c8; - border-color: #e9aeae; - border: 1px solid; - color: #c33; - border-radius: 0 5px 5px 0; - padding: 5px; + background-color: #f7c8c8; + border: 1px solid #e9aeae; + color: #c33; + border-radius: 0 5px 5px 0; + padding: 5px; } .file-diff { - overflow: auto; + overflow: auto; } .file-diff > div { - width: 100%: + width: 100%; } pre { - margin: 0; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - line-height: 18px; - text-indent: 5px; - color: #333; - border: solid #eeeeee; - border-width: 0 1px 0 0; - cursor: pointer; + margin: 0; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; + line-height: 18px; + text-indent: 5px; + color: #333; + border: solid #eeeeee; + border-width: 0 1px 0 0; + cursor: pointer; } .code-wrapper { - overflow-x: auto; - overflow-y: hidden; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; + overflow-x: auto; + overflow-y: hidden; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; } .diff-table { - border-collapse: separate; + border-collapse: separate; } table { - border-collapse: collapse; - border-spacing: 0; + border-collapse: collapse; + border-spacing: 0; } tbody { - display: table-row-group; - vertical-align: middle; - border-color: inherit; + display: table-row-group; + vertical-align: middle; + border-color: inherit; } tr { - display: table-row; - vertical-align: inherit; - border-color: inherit; + display: table-row; + vertical-align: inherit; + border-color: inherit; } td, th { - padding: 0; + padding: 0; +} + +pre { + text-indent: 0; + border: none; } .code-linenumber { - width: 1%; - min-width: 50px; - padding-left: 10px; - padding-right: 10px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - line-height: 18px; - color: rgba(0,0,0,0.3); - vertical-align: top; - text-align: right; - border: solid #eeeeee; - border-width: 0 1px 0 0; - cursor: pointer; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + width: 1%; + min-width: 25px; + padding-left: 10px; + padding-right: 10px; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; + line-height: 18px; + color: rgba(0, 0, 0, 0.3); + vertical-align: top; + text-align: right; + border: solid #eeeeee; + border-width: 0 1px 0 0; + cursor: pointer; } .code-line { - position: relative; - padding-left: 10px; - padding-right: 10px; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - color: #333; - vertical-align: top; - white-space: pre; - overflow: visible; + position: relative; + padding-left: 10px; + padding-right: 10px; + font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; + color: #333; + vertical-align: top; + white-space: pre; + overflow: visible; } .delete { - vertical-align: middle; - background-color: #f7c8c8; - border-color: #e9aeae; + vertical-align: middle; + background-color: #f7c8c8; + border-color: #e9aeae; } .insert { - vertical-align: middle; - background-color: #ceffce; - border-color: #b4e2b4; + vertical-align: middle; + background-color: #ceffce; + border-color: #b4e2b4; } .info { - background-color: #f8fafd; - vertical-align: middle; - color: rgba(0,0,0,0.3); - border-color: #d5e4f2; + background-color: #f8fafd; + vertical-align: middle; + color: rgba(0, 0, 0, 0.3); + border-color: #d5e4f2; } .code-line del { - display: inline-block; - margin-top: -1px; - text-decoration: none; - background-color: #ffb6ba; - font-weight: 700; + display: inline-block; + margin-top: -1px; + text-decoration: none; + background-color: #ffb6ba; + font-weight: 700; } .code-line ins { - display: inline-block; - margin-top: -1px; - text-decoration: none; - background-color: #97f295; - font-weight: 700; + display: inline-block; + margin-top: -1px; + text-decoration: none; + background-color: #97f295; + font-weight: 700; } diff --git a/template.html b/template.html index dd5a384..5cd37f5 100644 --- a/template.html +++ b/template.html @@ -1,199 +1,61 @@ - - Diff to HTML by rtfpessoa + + Diff to HTML by rtfpessoa - + - + - - - - + + + $(document).ready(function () { + var content = Diff2Html.getPrettyHtmlFromDiff(exInput); + $("body").html(content); + }); + -
-
diff --git a/word-diff-parser.js b/word-diff-parser.js deleted file mode 100644 index 26938e2..0000000 --- a/word-diff-parser.js +++ /dev/null @@ -1,136 +0,0 @@ -/* - * - * Word Diff Parser (word-diff-parser.js) - * Author: rtfpessoa - * Date: Saturday 30 August 2014 - * - */ - -(function($, window) { - var ClassVariable; - - ClassVariable = (function() { - - function WordDiffParser() {} - - WordDiffParser.prototype.generateChangedWords = function(wordDiffInput) { - return wordDiffInput ? parseChangedWords(wordDiffInput) : null; - }; - - var parseChangedWords = function(wordDiffInput) { - var files = [], - currentFile = null, - oldLine = null, - newLine = null; - - wordDiffInput.split("\n").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 || line.charAt(0) === "*" || - line.indexOf("new") === 0 || - line.indexOf("index") === 0 || - line.indexOf("---") === 0 || - line.indexOf("+++") === 0) { - return; - } - - if (line.indexOf("diff") === 0) { - /* File Diff Line */ - - /* add previous file(if exists) before start a new one */ - if (currentFile && - (currentFile.addedWords.length || currentFile.deletedWords.length)) { - files.push(currentFile); - currentFile = null; - } - - /* create file structure */ - currentFile = {}; - currentFile.addedWords = []; - currentFile.deletedWords = []; - - /* 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]; - - } else if (line.indexOf("@@") === 0) { - /* Diff Block Header Line */ - - var values = /^(@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*/.exec(line); - - oldLine = values[2]; - newLine = values[4]; - - } else { - /* Regular Diff Line */ - - var addedWords = []; - if (addedWords = line.match(/\{\+(.+?)\+\}/g)) { - addedWords = addedWords.map(function(word) { - return cleanWordMatch(word); - }); - } else { - addedWords = []; - } - - var deletedWords = []; - if (deletedWords = line.match(/\[-(.+?)-\]/g)) { - deletedWords = deletedWords.map(function(word) { - return cleanWordMatch(word); - }); - } else { - deletedWords = []; - } - - if (!addedWords.length && !deletedWords.length) { - oldLine++; - newLine++; - } else { - if (addedWords.length) { - currentFile.addedWords[newLine] = addedWords; - newLine++; - } - - if (deletedWords.length) { - currentFile.deletedWords[oldLine] = deletedWords; - oldLine++; - } - } - } - }); - - /* add previous file(if exists) before start a new one */ - if (currentFile && - (currentFile.addedWords.length || currentFile.deletedWords.length)) { - files.push(currentFile); - currentFile = null; - } - - return files; - }; - - var cleanWordMatch = function(str) { - return str.substr(2, str.length - 4); - }; - - /* singleton pattern */ - var instance; - return { - getInstance: function() { - if (instance === undefined) { - instance = new WordDiffParser(); - /* Hide the constructor so the returned objected can't be new'd */ - instance.constructor = null; - } - return instance; - } - }; - - })(); - - window.WordDiffParser = ClassVariable; - return window.WordDiffParser; - -})(jQuery, window);