refactor: Split code into individual files and tidier code (#160)

* refactor: split code into separate files

* chore: removed unused test code

* refactor: no longer depends on jQuery.extend

* docs: merged changes into CHANGELOG.md

Co-authored-by: Jamie Peabody <jpeabody@axway.com>
This commit is contained in:
Jamie Peabody
2021-11-21 17:22:04 +00:00
committed by GitHub
parent dfdba3c020
commit 79cb0ddd6a
22 changed files with 2099 additions and 7605 deletions

28
src/diff-parser.js Normal file
View File

@@ -0,0 +1,28 @@
const changeExp = new RegExp(/(^(?![><\-])*\d+(?:,\d+)?)([acd])(\d+(?:,\d+)?)/);
function DiffParser(diff) {
const changes = [];
let change_id = 0;
// parse diff
const diff_lines = diff.split(/\n/);
for (var i = 0; i < diff_lines.length; ++i) {
if (diff_lines[i].length == 0) continue;
const change = {};
const test = changeExp.exec(diff_lines[i]);
if (test == null) continue;
// lines are zero-based
const fr = test[1].split(',');
change['lhs-line-from'] = fr[0] - 1;
if (fr.length == 1) change['lhs-line-to'] = fr[0] - 1;
else change['lhs-line-to'] = fr[1] - 1;
const to = test[3].split(',');
change['rhs-line-from'] = to[0] - 1;
if (to.length == 1) change['rhs-line-to'] = to[0] - 1;
else change['rhs-line-to'] = to[1] - 1;
change['op'] = test[2];
changes[change_id++] = change;
}
return changes;
};
module.exports = DiffParser;

1479
src/diff-view.js Normal file

File diff suppressed because one or more lines are too long

291
src/diff.js Normal file
View File

@@ -0,0 +1,291 @@
function diff(lhs, rhs, options = {}) {
const {
ignorews = false,
ignoreaccents = false,
ignorecase = false
} = options;
this.codeify = new CodeifyText(lhs, rhs, {
ignorews,
ignoreaccents,
ignorecase
});
const lhs_ctx = {
codes: this.codeify.getCodes('lhs'),
modified: {}
};
const rhs_ctx = {
codes: this.codeify.getCodes('rhs'),
modified: {}
};
const vector_d = [];
const vector_u = [];
this._lcs(lhs_ctx, 0, lhs_ctx.codes.length, rhs_ctx, 0, rhs_ctx.codes.length, vector_u, vector_d);
this._optimize(lhs_ctx);
this._optimize(rhs_ctx);
this.items = this._create_diffs(lhs_ctx, rhs_ctx);
};
diff.prototype.changes = function() {
return this.items;
};
diff.prototype.getLines = function(side) {
return this.codeify.getLines(side);
};
diff.prototype.normal_form = function() {
let nf = '';
for (let index = 0; index < this.items.length; ++index) {
const item = this.items[index];
let lhs_str = '';
let rhs_str = '';
let change = 'c';
if (item.lhs_deleted_count == 0 && item.rhs_inserted_count > 0) change = 'a';
else if (item.lhs_deleted_count > 0 && item.rhs_inserted_count == 0) change = 'd';
if (item.lhs_deleted_count == 1) lhs_str = item.lhs_start + 1;
else if (item.lhs_deleted_count == 0) lhs_str = item.lhs_start;
else lhs_str = (item.lhs_start + 1) + ',' + (item.lhs_start + item.lhs_deleted_count);
if (item.rhs_inserted_count == 1) rhs_str = item.rhs_start + 1;
else if (item.rhs_inserted_count == 0) rhs_str = item.rhs_start;
else rhs_str = (item.rhs_start + 1) + ',' + (item.rhs_start + item.rhs_inserted_count);
nf += lhs_str + change + rhs_str + '\n';
const lhs_lines = this.getLines('lhs');
const rhs_lines = this.getLines('rhs');
if (rhs_lines && lhs_lines) {
let i;
// if rhs/lhs lines have been retained, output contextual diff
for (i = item.lhs_start; i < item.lhs_start + item.lhs_deleted_count; ++i) {
nf += '< ' + lhs_lines[i] + '\n';
}
if (item.rhs_inserted_count && item.lhs_deleted_count) nf += '---\n';
for (i = item.rhs_start; i < item.rhs_start + item.rhs_inserted_count; ++i) {
nf += '> ' + rhs_lines[i] + '\n';
}
}
}
return nf;
};
diff.prototype._lcs = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_lower] == rhs_ctx.codes[rhs_lower]) ) {
++lhs_lower;
++rhs_lower;
}
while ( (lhs_lower < lhs_upper) && (rhs_lower < rhs_upper) && (lhs_ctx.codes[lhs_upper - 1] == rhs_ctx.codes[rhs_upper - 1]) ) {
--lhs_upper;
--rhs_upper;
}
if (lhs_lower == lhs_upper) {
while (rhs_lower < rhs_upper) {
rhs_ctx.modified[ rhs_lower++ ] = true;
}
}
else if (rhs_lower == rhs_upper) {
while (lhs_lower < lhs_upper) {
lhs_ctx.modified[ lhs_lower++ ] = true;
}
}
else {
const sms = this._sms(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d);
this._lcs(lhs_ctx, lhs_lower, sms.x, rhs_ctx, rhs_lower, sms.y, vector_u, vector_d);
this._lcs(lhs_ctx, sms.x, lhs_upper, rhs_ctx, sms.y, rhs_upper, vector_u, vector_d);
}
};
diff.prototype._sms = function(lhs_ctx, lhs_lower, lhs_upper, rhs_ctx, rhs_lower, rhs_upper, vector_u, vector_d) {
const max = lhs_ctx.codes.length + rhs_ctx.codes.length + 1;
const kdown = lhs_lower - rhs_lower;
const kup = lhs_upper - rhs_upper;
const delta = (lhs_upper - lhs_lower) - (rhs_upper - rhs_lower);
const odd = (delta & 1) != 0;
const offset_down = max - kdown;
const offset_up = max - kup;
const maxd = ((lhs_upper - lhs_lower + rhs_upper - rhs_lower) / 2) + 1;
vector_d[ offset_down + kdown + 1 ] = lhs_lower;
vector_u[ offset_up + kup - 1 ] = lhs_upper;
const ret = { x:0, y:0 }
let x;
let y;
for (let d = 0; d <= maxd; ++d) {
for (let k = kdown - d; k <= kdown + d; k += 2) {
if (k == kdown - d) {
x = vector_d[ offset_down + k + 1 ];//down
}
else {
x = vector_d[ offset_down + k - 1 ] + 1;//right
if ((k < (kdown + d)) && (vector_d[ offset_down + k + 1 ] >= x)) {
x = vector_d[ offset_down + k + 1 ];//down
}
}
y = x - k;
// find the end of the furthest reaching forward D-path in diagonal k.
while ((x < lhs_upper) && (y < rhs_upper) && (lhs_ctx.codes[x] == rhs_ctx.codes[y])) {
x++; y++;
}
vector_d[ offset_down + k ] = x;
// overlap ?
if (odd && (kup - d < k) && (k < kup + d)) {
if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
ret.x = vector_d[offset_down + k];
ret.y = vector_d[offset_down + k] - k;
return (ret);
}
}
}
// Extend the reverse path.
for (k = kup - d; k <= kup + d; k += 2) {
// find the only or better starting point
if (k == kup + d) {
x = vector_u[offset_up + k - 1]; // up
} else {
x = vector_u[offset_up + k + 1] - 1; // left
if ((k > kup - d) && (vector_u[offset_up + k - 1] < x))
x = vector_u[offset_up + k - 1]; // up
}
y = x - k;
while ((x > lhs_lower) && (y > rhs_lower) && (lhs_ctx.codes[x - 1] == rhs_ctx.codes[y - 1])) {
// diagonal
x--;
y--;
}
vector_u[offset_up + k] = x;
// overlap ?
if (!odd && (kdown - d <= k) && (k <= kdown + d)) {
if (vector_u[offset_up + k] <= vector_d[offset_down + k]) {
ret.x = vector_d[offset_down + k];
ret.y = vector_d[offset_down + k] - k;
return (ret);
}
}
}
}
throw "the algorithm should never come here.";
};
diff.prototype._optimize = function(ctx) {
let start = 0;
let end = 0;
while (start < ctx.codes.length) {
while ((start < ctx.codes.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
start++;
}
end = start;
while ((end < ctx.codes.length) && (ctx.modified[end] == true)) {
end++;
}
if ((end < ctx.codes.length) && (ctx.codes[start] == ctx.codes[end])) {
ctx.modified[start] = false;
ctx.modified[end] = true;
}
else {
start = end;
}
}
};
diff.prototype._create_diffs = function(lhs_ctx, rhs_ctx) {
const items = [];
let lhs_start = 0;
let rhs_start = 0;
let lhs_line = 0;
let rhs_line = 0;
while (lhs_line < lhs_ctx.codes.length || rhs_line < rhs_ctx.codes.length) {
if ((lhs_line < lhs_ctx.codes.length) && (!lhs_ctx.modified[lhs_line])
&& (rhs_line < rhs_ctx.codes.length) && (!rhs_ctx.modified[rhs_line])) {
// equal lines
lhs_line++;
rhs_line++;
}
else {
// maybe deleted and/or inserted lines
lhs_start = lhs_line;
rhs_start = rhs_line;
while (lhs_line < lhs_ctx.codes.length && (rhs_line >= rhs_ctx.codes.length || lhs_ctx.modified[lhs_line]))
lhs_line++;
while (rhs_line < rhs_ctx.codes.length && (lhs_line >= lhs_ctx.codes.length || rhs_ctx.modified[rhs_line]))
rhs_line++;
if ((lhs_start < lhs_line) || (rhs_start < rhs_line)) {
// store a new difference-item
items.push({
lhs_start: lhs_start,
rhs_start: rhs_start,
lhs_deleted_count: lhs_line - lhs_start,
rhs_inserted_count: rhs_line - rhs_start
});
}
}
}
return items;
};
function CodeifyText(lhs, rhs, options) {
this._max_code = 0;
this._diff_codes = {};
this.ctxs = {};
this.options = options;
if (typeof lhs === 'string') {
this.lhs = lhs.split('\n');
} else {
this.lhs = lhs;
}
if (typeof rhs === 'string') {
this.rhs = rhs.split('\n');
} else {
this.rhs = rhs;
}
};
CodeifyText.prototype.getCodes = function(side) {
if (!this.ctxs.hasOwnProperty(side)) {
var ctx = this._diff_ctx(this[side]);
this.ctxs[side] = ctx;
ctx.codes.length = Object.keys(ctx.codes).length;
}
return this.ctxs[side].codes;
}
CodeifyText.prototype.getLines = function(side) {
return this.ctxs[side].lines;
}
CodeifyText.prototype._diff_ctx = function(lines) {
var ctx = {i: 0, codes: {}, lines: lines};
this._codeify(lines, ctx);
return ctx;
}
CodeifyText.prototype._codeify = function(lines, ctx) {
var code = this._max_code;
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
if (this.options.ignorews) {
line = line.replace(/\s+/g, '');
}
if (this.options.ignorecase) {
line = line.toLowerCase();
}
if (this.options.ignoreaccents) {
line = line.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
var aCode = this._diff_codes[line];
if (aCode != undefined) {
ctx.codes[i] = aCode;
}
else {
this._max_code++;
this._diff_codes[line] = this._max_code;
ctx.codes[i] = this._max_code;
}
}
}
module.exports = diff;

90
src/lcs.js Normal file
View File

@@ -0,0 +1,90 @@
const diff = require('./diff');
const DiffParser = require('./diff-parser');
function LCS(x, y, options) {
function getPositionOfWords(text, options) {
var exp = new RegExp(/\w+/g);
var map = {};
var match;
var item = 0;
var p0 = 0;
var p1;
var ws0;
while ((match = exp.exec(text))) {
// if previous ws, then calculate whether not not this should be
// included as part of the diff
if (!options.ignorews && ws0 && ws0 <= match.index - 1) {
map[ item++ ] = {
p0: ws0,
p1: match.index - 1,
ws0,
word: text.slice(ws0, match.index)
};
}
// add the words to be matched in diff
p0 = match.index;
p1 = p0 + match[0].length - 1;
ws0 = p1 + 2;
map[ item++ ] = {
p0,
p1,
ws0,
word: text.slice(p0, p1 + 1)
};
}
return map;
}
this.options = options;
if (options.ignorews) {
this.xmap = getPositionOfWords(x, this.options);
this.ymap = getPositionOfWords(y, this.options);
const xmap = this.xmap;
this.x = Object.keys(xmap).map(function (i) { return xmap[i].word; });
const ymap = this.ymap;
this.y = Object.keys(ymap).map(function (i) { return ymap[i].word; });
} else {
this.x = x.split('');
this.y = y.split('');
}
};
LCS.prototype.clear = function () {
this.ready = 0;
};
LCS.prototype.diff = function (added, removed) {
const d = new diff(this.x, this.y, {
ignorews: !!this.options.ignorews,
ignoreaccents: !!this.options.ignoreaccents
});
const changes = DiffParser(d.normal_form());
for (let i = 0; i < changes.length; ++i) {
const change = changes[i];
if (this.options.ignorews) {
if (change.op != 'a') {
const from = this.xmap[change['lhs-line-from']].p0;
const to = this.xmap[change['lhs-line-to']].p1 + 1;
removed(from, to);
}
if (change.op !== 'd') {
const from = this.ymap[change['rhs-line-from']].p0;
const to = this.ymap[change['rhs-line-to']].p1 + 1;
added(from, to);
}
} else {
if (change.op != 'a') {
const from = change['lhs-line-from'];
const to = change['lhs-line-to'] + 1;
removed(from, to);
}
if (change.op !== 'd') {
const from = change['rhs-line-from'];
const to = change['rhs-line-to'] + 1;
added(from, to);
}
}
}
};
module.exports = LCS;

File diff suppressed because one or more lines are too long

View File

@@ -4,8 +4,8 @@ class Timer {
}
static stop() {
var t1 = Date.now();
var td = t1 - Timer.t0;
const t1 = Date.now();
const td = t1 - Timer.t0;
Timer.t0 = t1;
return td;
}