Skip to content

Instantly share code, notes, and snippets.

@stevemk14ebr
Created July 28, 2022 01:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevemk14ebr/41a95bd1e47f9efd520ce30b0fb2cfc5 to your computer and use it in GitHub Desktop.
Save stevemk14ebr/41a95bd1e47f9efd520ce30b0fb2cfc5 to your computer and use it in GitHub Desktop.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
<title>Patch Viewer</title>
<style type="text/css">
body, h1, h2, h3, p {
font-family: sans-serif;
}
pre {
white-space:pre-wrap;
word-break: break-all;
word-wrap: break-word;
padding: 0;
margin: 0;
}
td {
padding: 0;
margin: 0;
}
textarea {
margin: 0;
padding: 0;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.result_table {
padding: 0;
margin: 0;
border: 0.1em;
border-spacing: 0;
border-left-style: solid;
border-right-style: none;
border-top-style: none;
border-bottom-style: solid;
border-color: #BFBFBF;
color: black;
width: 100%;
}
.code_row {
width: 100%;
}
.linenum_cell {
width: 0%;
border-right-style: solid;
border-right-width: 0.1em;
border-color: #F0F0F0;
background-color: #F7F7F7;
color: #999999;
text-align: right;
padding-left: 0.2em;
padding-right: 0.2em;
user-select: none;
}
.linenum_text {
white-space: nowrap;
word-break: normal;
word-wrap: normal;
}
.diff_basic {
width: 50%;
border-right-style: solid;
border-right-width: 0.1em;
border-color: #BFBFBF;
}
.diff_context {
background-color: white;
color: #4D4D4D;
}
.diff_add {
background-color: #C0FFC0;
}
.diff_mod {
background-color: #FFFFB0;
}
.diff_del {
background-color: #FFD0D0;
color: #4D4D4D;
/* text-decoration: line-through; */
}
.diff_pad {
background-color: #F0F0F0;
}
.diff_info {
padding-top: 0.1em;
padding-bottom: 0.1em;
padding-left: 0.4em;
padding-right: 0.4em;
}
.diff_split {
background-color: #EEEEEE;
color: #4D4D4D;
text-align: center;
border-top-style: solid;
border-top-width: 0.1em;
border-bottom-style: solid;
border-bottom-width: 0.1em;
}
.diff_sec_title {
width: 100%;
background-color: #DDDDCC;
color: #4D4D4D;
font-weight: bold;
border-top-style: solid;
border-top-width: 0.1em;
}
.toolbar {
background-color: #EEEEEE;
padding: 0.6em;
border-radius: 0.6em;
}
.nav_filename {
font-weight: bold;
font-size: large;
}
.nav_filename_bar {
padding-top: 0.5em;
padding-bottom: 0.5em;
}
.nav_link {
text-decoration: none;
color: black;
font-family: monospace;
padding-left: 0.3em;
padding-right: 0.3em;
}
.nav_link_bar {
background: #EEEEEE;
border-radius: 0.4em;
border-style: solid;
border-width: 0.1em;
border-color: #BFBFBF;
}
.nav_filelist {
font-family: monospace;
}
.nav_filelist_bar {
padding-top: 0.5em;
}
</style>
<script type="text/javascript" language="JavaScript">
function diffRender(diff, node) {
// Common rendering engine STM states
var LINE_CONTEXT = 0; // unmodified context block, should copy to both sideA & B
var LINE_ADD = 1; // + block, sideB only
var LINE_DEL = 2; // - block, sideA only
var LINE_MOD = 3; // ! block
var LINE_SPLIT = 4; // section split, should be start line number
var LINE_SEC_TITLE = 5; // section title, sideA only
// Line buffers to hold the file parse result
var sideA = new Array();
var sideB = new Array();
// Add Navigation bar into DOM node.
function addNav(node, prefix, name) {
function newNavLink(href, inner, name) {
var link = document.createElement("a");
link.setAttribute("href", href);
link.innerHTML = inner;
link.setAttribute("class", "nav_link");
if (name != null) {
link.setAttribute("name", name);
}
return (link);
}
var fileNames = node.getElementsByClassName("nav_filename");
if (fileNames.length <= 1) {
return;
}
// create file navigation bar
var defaultItem = document.createElement("option");
defaultItem.setAttribute("value", "#" + prefix + "TOP");
defaultItem.setAttribute("selected", "selected");
defaultItem.appendChild(document.createTextNode("< Jump to ... >"));
var title = document.createElement("span");
title.innerHTML = "<a name=" + prefix + "TOP></a><b>File List:&nbsp;</b>";
var list = document.createElement("select");
list.setAttribute("onchange", "location.href = this.options[this.selectedIndex].value;");
list.setAttribute("class", "nav_filelist");
list.appendChild(defaultItem);
var bar = document.createElement("div");
bar.setAttribute("class", "nav_filelist_bar");
bar.appendChild(list);
bar.insertBefore(title, list);
for (var i = fileNames.length - 1; i >= 0; i--) {
var fileName = fileNames[i];
var fileNum = i + 1;
// Add file name into select list
var item = document.createElement("option");
item.setAttribute("value", "#" + prefix + fileNum);
item.appendChild(document.createTextNode(fileName.textContent));
list.insertBefore(item, list.firstChild);
// Add Prev/Next/Tops links before each title, set anchor on TOPS link
var smallbar = document.createElement("span");
smallbar.setAttribute("class", "nav_link_bar");
smallbar.appendChild(newNavLink("#" + prefix + (fileNum - 1), "&laquo;", null));
smallbar.appendChild(newNavLink("#" + prefix + (fileNum + 1), "&raquo;", null));
smallbar.appendChild(newNavLink("#" + prefix + "TOP", "&uArr;", prefix + fileNum));
// insert the small nav bar before the file name, and add a space between them
fileName.parentNode.insertBefore(smallbar, fileName);
fileName.insertBefore(document.createTextNode(" "), fileName.firstChild);
}
// Insert the navigation bar before the first item, and place the default item to first
list.removeChild(defaultItem);
list.insertBefore(defaultItem, list.firstChild);
node.insertBefore(bar, node.firstChild);
} // addNav
// Render a file buffer
function renderFile(fileName, node) {
var sideALn;
var sideBLn;
var sect_title = null;
// insert file name
function insertFilename(node, fileName) {
var title = document.createElement("span");
title.setAttribute("class", "nav_filename");
title.appendChild(document.createTextNode((fileName == "") ? "Unknown" : fileName));
var bar = document.createElement("div");
bar.setAttribute("class", "nav_filename_bar");
bar.appendChild(title);
node.appendChild(bar);
}
// rendering STM state handler
function processState(tb, parseState) {
// render a row
function insertRow(tb, codeA, codeB, type) {
// create a line number table cell
function newLineNumCell(count, type) {
var td = document.createElement("td");
var pre = document.createElement("pre");
td.setAttribute("class", "linenum_cell");
pre.setAttribute("class", "linenum_text");
pre.appendChild(document.createTextNode(count));
td.appendChild(pre);
return (td);
}
// create a table cell for code
function newCodeCell(isSideB, text, type) {
var td = document.createElement("td");
var pre = document.createElement("pre");
switch (type) {
case LINE_CONTEXT:
td.setAttribute("class", "diff_basic diff_context");
break;
case LINE_SPLIT:
td.setAttribute("class", "diff_basic diff_info diff_split");
td.setAttribute("colspan", "2");
break;
case LINE_SEC_TITLE:
td.setAttribute("class", "diff_basic diff_info diff_sec_title");
td.setAttribute("colspan", "4");
text = "Section: " + text;
break;
case LINE_ADD:
if (isSideB) {
td.setAttribute("class", "diff_basic diff_add");
} else {
td.setAttribute("class", "diff_basic diff_del");
}
break;
case LINE_DEL:
td.setAttribute("class", "diff_basic diff_pad");
td.setAttribute("colspan", "2");
break;
case LINE_MOD:
if (text == null) {
td.setAttribute("class", "diff_basic diff_pad");
td.setAttribute("colspan", "2");
} else {
td.setAttribute("class", "diff_basic diff_mod");
}
break;
default:
}
if (text == null) {
text = "";
}
pre.appendChild(document.createTextNode(text));
td.appendChild(pre);
return (td);
} // newCodeCell()
// check if we need to create line number cells in this side
function needLineNum(text, type) {
if ((type == LINE_SPLIT) || (type == LINE_SEC_TITLE)) {
return (false);
}
return (text != null);
}
// skip the dummy end mark
if ((type == LINE_SPLIT) && (codeA == null) && (codeB == null)) {
return;
}
var tr = document.createElement("tr");
var typeA = type, typeB = type;
switch (type) {
case LINE_ADD:
typeA = LINE_DEL;
break;
case LINE_DEL:
typeA = LINE_ADD;
break;
default:
}
tr.setAttribute("class", "code_row");
if (needLineNum(codeA, type)) {
tr.appendChild(newLineNumCell(sideALn, typeA));
sideALn++;
}
tr.appendChild(newCodeCell(false, codeA, typeA));
if (type != LINE_SEC_TITLE) {
if (needLineNum(codeB, type)) {
tr.appendChild(newLineNumCell(sideBLn, typeA));
sideBLn++;
}
tr.appendChild(newCodeCell(true, codeB, typeB));
}
tb.appendChild(tr);
} // insertRow()
var lineA = sideA[0];
var lineB = sideB[0];
// process current work first
switch (parseState) {
// Just sync between diff segments
case LINE_SPLIT:
// print sec title if we have one
var hasTitle = (sect_title != null);
if (hasTitle) {
insertRow(tb, sect_title, null, LINE_SEC_TITLE);
}
sect_title = null;
// Update the line number from the split text
var lnA = parseInt(lineA.text);
var lnB = parseInt(lineB.text);
// check if we can convert line number to skipped line
var skipped = lnA - sideALn;
if ((lnA > sideALn) && (skipped == (lnB - sideBLn))) {
var split_text = skipped + " unchanged line"
+ ((skipped == 1) ? "" : "s")
+ " skipped...";
insertRow(tb, split_text, split_text, parseState);
} else {
if ((lnA == 0) || (lnA == 1) || (lnB == 0) || (lnB == 1)) {
// Don't print the split
if (!hasTitle) {
tb.setAttribute("style", "border-top-style: solid");
}
} else {
insertRow(tb, lineA.text, lineB.text, parseState);
}
}
sideALn = lnA;
sideBLn = lnB;
sideA.shift();
sideB.shift();
break;
// section title (diffs attached in DDTS have such title)
case LINE_SEC_TITLE:
// cache the section title, will print it after reached next split
sect_title = lineA.text;
sideA.shift();
break;
// we are in the context section of diff
case LINE_CONTEXT:
if (lineB.state != LINE_CONTEXT) {
// only left side has the context (del only)
insertRow(tb, lineA.text, lineA.text, parseState);
sideA.shift();
break;
}
if (lineA.state != LINE_CONTEXT) {
// only right side has the context (add only)
insertRow(tb, lineB.text, lineB.text, parseState);
sideB.shift();
break;
}
// both have context (modify)
// in normal the context should be the same, but we'd better
// to check that
if (lineA.text == lineB.text) {
insertRow(tb, lineA.text, lineB.text, parseState);
sideA.shift();
sideB.shift();
} else {
// not quite sure what happened, but just display context
// from left first
insertRow(tb, lineA.text, lineA.text, parseState);
sideA.shift();
}
break;
// we are in the add section of diff, it could only be in the right side
case LINE_ADD:
insertRow(tb, null, lineB.text, parseState);
sideB.shift();
break;
// we are in the del section of diff, it could only be in the left side
case LINE_DEL:
insertRow(tb, lineA.text, null, parseState);
sideA.shift();
break;
// we are in the modified section of diff
case LINE_MOD:
var modA = null, modB = null;
if (lineA.state == LINE_MOD) {
modA = lineA.text;
sideA.shift();
}
if (lineB.state == LINE_MOD) {
modB = lineB.text;
sideB.shift();
}
insertRow(tb, modA, modB, parseState);
break;
default:
}
} // processState()
// detect the next FSM state for rendering engine
function detectNextState() {
// now let's decide next state, make sure we have end-marks in both sides
if (sideA.length == 0) {
pushLineTo(sideA, null, LINE_SPLIT);
}
if (sideB.length == 0) {
pushLineTo(sideB, null, LINE_SPLIT);
}
var lineA = sideA[0];
var lineB = sideB[0];
// left reached the end
if (lineA.state == LINE_SPLIT) {
return (lineB.state);
}
// right reached the end
if (lineB.state == LINE_SPLIT) {
return (lineA.state);
}
// the priority is :
// SectionTitle > Del > Add > Modify > Context
if ((lineA.state == LINE_SEC_TITLE)
|| (lineA.state == LINE_DEL)) {
return (lineA.state);
}
if (lineB.state == LINE_ADD) {
return (lineB.state);
}
if ((lineA.state == LINE_MOD) || (lineB.state == LINE_MOD)) {
return (LINE_MOD);
}
return (LINE_CONTEXT);
} // detectNextState()
if (bufferEmpty()) {
return;
}
sideALn = 1;
sideBLn = 1;
var tb = document.createElement("table");
while ((sideA.length > 0) || (sideB.length > 0)) {
processState(tb, detectNextState());
}
tb.setAttribute("class", "result_table");
insertFilename(node, fileName);
node.appendChild(tb);
} // renderFile()
// Push a line to file buffer
function pushLineTo(side, lineText, lineState) {
var lineObj = new Object();
lineObj.text = lineText;
lineObj.state = lineState;
side.push(lineObj);
}
// Check if file buffer is empty
function bufferEmpty() {
return ((sideA.length == 0) && (sideB.length == 0));
}
// Parse the diff generated by Cisco acme/cctools
// Supports common context diff format as well
function contextDiffParser(diff, node) {
// Patch file parser STM states
var ST_IN_META = 100; // meta (Index: xxx, ***, etc)
var ST_IN_ORG = 200; // orignal file (sideA)
var ST_IN_NEW = 300; // new file (sideB)
var state = ST_IN_META;
// push line to the correct side by current parser FSM state
function pushLine(lineText, lineState) {
if (lineState == LINE_SEC_TITLE) {
pushLineTo(sideA, lineText, lineState);
return;
}
switch (state) {
case ST_IN_ORG:
pushLineTo(sideA, lineText, lineState);
break;
case ST_IN_NEW:
pushLineTo(sideB, lineText, lineState);
break;
default:
}
}
var lines = diff.split("\n");
var fileName = "";
var fileNameSrc = ""; // Where the file name from
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var result;
// - xxxxx (removed lines)
if (((result = /^- ([^\n]*)/.exec(line)) != null)
|| ((result = /^-($)/.exec(line)) != null)) {
pushLine(result[1], LINE_DEL);
continue;
}
// + xxxxx (added lines)
if (((result = /^\+ ([^\n]*)/.exec(line)) != null)
|| ((result = /^\+($)/.exec(line)) != null)) {
pushLine(result[1], LINE_ADD);
continue;
}
// ! xxxxx (modified lines)
if (((result = /^! ([^\n]*)/.exec(line)) != null)
|| ((result = /^!($)/.exec(line)) != null)) {
pushLine(result[1], LINE_MOD);
continue;
}
// *** 5,20 **** (sideA line number)
if ((result = /^\*{3} ([0-9]+)[0-9,]* \*{3}/.exec(line)) != null) {
state = ST_IN_ORG;
pushLine(result[1], LINE_SPLIT);
continue;
}
// --- 11,20 ---- (sideB line number)
if ((result = /^-{3} ([0-9]+)[0-9,]* -{3}/.exec(line)) != null) {
state = ST_IN_NEW;
pushLine(result[1], LINE_SPLIT);
continue;
}
// For indexed diff:
// Index: filename
//
// For unified diff filename:
// *** /path/to/original
// --- /path/to/new
if (((result = /^Index: ([^\n]*)/.exec(line)) != null)
|| ((result = /^\*{3} ([^\n]*)/.exec(line)) != null)
|| ((result = /^\-{3} ([^\n]*)/.exec(line)) != null)) {
if (!bufferEmpty()) {
// this should be the file split
renderFile(fileName, node);
fileName = "";
fileNameSrc = "";
}
// if we have not get the file name yet, or the previous file name
// was parsed from the same source (index/left/right), and current
// file name is valid, use it.
if (((fileName == "") || (fileNameSrc == line.charAt(0)))
&& (result[1] != "")
&& (result[1] != "/dev/null")) {
fileName = result[1];
fileNameSrc = line.charAt(0);
}
state = ST_IN_META;
continue;
}
// context lines
if (((result = /^ ([^\n]*)/.exec(line)) != null)
|| ((result = /^ ($)/.exec(line)) != null)) {
pushLine(result[1], LINE_CONTEXT);
continue;
}
// *************** (optional section title)
if ((result = /^\*{15} ([^\n]*)/.exec(line)) != null) {
pushLine(result[1], LINE_SEC_TITLE);
continue;
}
state = ST_IN_META;
}
// for last file
renderFile(fileName, node);
} // contextDiffParser()
// Parse the diff generated by git
// Supports common unified diff format as well
function unifiedDiffParser(diff, node) {
// Patch file parser STM states
var ST_IN_META = 100; // meta (Index: xxx, +++, ---, etc)
var ST_IN_DIFF = 200; // valid diff (incl. @@, +, -, etc)
// Substate for diff part
var ST_IN_DIFF_CTX = 220; // context lines
var ST_IN_DIFF_ADD = 230; // +xxxxx
var ST_IN_DIFF_DEL = 240; // -xxxxx
var ST_IN_DIFF_MOD = 250; // modified block (- block followed by a + block)
var state = ST_IN_META;
var subState = 0;
// Push buffered - block to sideA with final block property
function pushDels(endDel, isModify) {
if (lastDel == -1) {
return;
}
for (var i = lastDel; i < endDel; i++) {
if (lines[i].substr(0, 1) == "-") {
pushLineTo(sideA, lines[i].substr(1), (isModify ? LINE_MOD : LINE_DEL));
}
}
lastDel = -1;
}
// Set the substate of parser STM. Will push buffered del block to file buffer
// when needed, or just update the del block start line.
function setSubState(newSubState, currLine) {
if (subState == newSubState) {
return;
}
if (subState == ST_IN_DIFF_DEL) {
pushDels(currLine, (newSubState == ST_IN_DIFF_MOD));
} else if ((state == ST_IN_DIFF) && (newSubState == ST_IN_DIFF_DEL)) {
lastDel = i;
}
subState = newSubState;
}
var lines = diff.split("\n");
var fileName = "";
var fileNameSrc = ""; // Where the file name from
var lastDel = -1;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var result;
// For indexed diff:
// Index: filename
//
// For git diff:
// diff --git a/filepath b/filepath
//
// For unified diff filename:
// --- /path/to/original ''timestamp''
// +++ /path/to/new ''timestamp''
if (((result = /^Index: ([^\n]*)/.exec(line)) != null)
|| ((result = /^diff --git .*? b\/([^\n]*)/.exec(line)) != null)
|| ((state == ST_IN_META) // avoid conflict with the real +/- lines
&& (((result = /^\-{3} ([^\n]*)/.exec(line)) != null)
|| ((result = /^\+{3} ([^\n]*)/.exec(line)) != null)))) {
setSubState(0, i);
if (!bufferEmpty()) {
// this should be the file split
renderFile(fileName, node);
fileName = "";
fileNameSrc = "";
}
// if we have not get the file name yet, or the previous file name
// was parsed from the same source (index/left/right), and current
// file name is valid, use it.
if (((fileName == "") || (fileNameSrc == line.charAt(0)))
&& (result[1] != "")
&& (result[1] != "/dev/null")) {
fileName = result[1];
fileNameSrc = line.charAt(0);
}
state = ST_IN_META;
continue;
}
// -xxxxxx (removed lines)
if (((result = /^-([^\n]*)/.exec(line)) != null)
&& (state == ST_IN_DIFF)) {
// the unified diff only use -/+ but no modified sign (!), so
// we have to see if there is + block next to current - block.
// If so, it should be render as modified block. We have to
// cache the - lines until we know the final block property.
setSubState(ST_IN_DIFF_DEL, i);
continue;
}
// +xxxxx (added lines)
if (((result = /^\+([^\n]*)/.exec(line)) != null)
&& (state == ST_IN_DIFF)) {
// If current + block just next to previous - block, both should be
// modified blocks. Or it just a normal + block.
var isModify = ((subState == ST_IN_DIFF_DEL)
|| (subState == ST_IN_DIFF_MOD));
setSubState((isModify ? ST_IN_DIFF_MOD : ST_IN_DIFF_ADD), i);
pushLineTo(sideB, result[1], (isModify ? LINE_MOD : LINE_ADD));
continue;
}
// \ No newline at end of file
if (((result = /^\\ ([^\n]*)/.exec(line)) != null)
&& (state == ST_IN_DIFF)) {
continue;
}
// @@ -184,3 +184,41 @@ (Optional section title)
if ((result = /^@@ \-([0-9]+)[0-9,]* \+([0-9]+)[0-9,]* @@ *([^\n]*)/.exec(line)) != null) {
setSubState(0, i);
state = ST_IN_DIFF;
// parse section title
if (result[3] != "") {
pushLineTo(sideA, result[3], LINE_SEC_TITLE);
}
// parse line number
pushLineTo(sideA, result[1], LINE_SPLIT);
pushLineTo(sideB, result[2], LINE_SPLIT);
continue;
}
// context lines
if (((result = /^ ([^\n]*)/.exec(line)) != null)
&& (state == ST_IN_DIFF)) {
setSubState(ST_IN_DIFF_CTX, i);
pushLineTo(sideA, result[1], LINE_CONTEXT);
pushLineTo(sideB, result[1], LINE_CONTEXT);
continue;
}
setSubState(0, i);
state = ST_IN_META;
}
// for last file
renderFile(fileName, node);
} // unifiedDiffParser()
// normalize the line endings
diff = diff.replace(/\r\n?/g, "\n");
// expand tab to 8 spaces
// diff = diff.replace(/\t/g, " ");
node.innerHTML = "";
if (diff.search(/(^|\n)@@ \-[0-9,]+ \+[0-9,]+ @@/) != -1) {
unifiedDiffParser(diff, node);
} else {
contextDiffParser(diff, node);
}
// Generate navigation bar and file list
addNav(node, "navFile");
} // diffRender()
function loadDiff(node) {
if (!window.FileReader) {
alert("This feature is not supported in this browser. \n"
+ "Run in Chrome/Firefox/Opera is highly recommended.");
return;
}
var files = document.getElementById("diff_file").files;
if (files.length == 0) {
alert("Please select a valid patch file.");
return;
}
var file = files[0];
var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
diffRender(this.result, node);
}
}
</script>
</head>
<body onload = "form_diff.reset()">
<h2>Side-by-Side Patch Viewer v0.5</h2>
<form id = "form_diff">
<div class = "toolbar">
Paste your diff snippet below then
<button
type = "button"
onclick = "diffRender(document.getElementById('diffText').value, document.getElementById('diffResult'))">
START
</button>
; or load from a file:
<input
id = "diff_file" type = "file" autocomplete = "off"
onchange = "loadDiff(document.getElementById('diffResult'))"/>
<div style = "padding-top: 0.3em">
<textarea rows = "8" id = "diffText"></textarea>
</div>
</div>
</form>
<div id = "diffResult" class = "context"></div>
<div>
<ul>
<li>Run in Chrome/Firefox/Opera is highly recommended. Internet Explorer might not support all the features of this tool.</li>
<li>Use the "Save As..." to save the side-by-side view of current patch.</li>
<li>Parsing large file might freeze your browser for minutes.</li>
<li>For any issues please contact Ding Zhaojie (<a href = "mailto:zhaojie_ding@msn.com">zhaojie_ding@msn.com</a>)</li>
</ul>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment