491 lines
13 KiB
C
491 lines
13 KiB
C
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
|
|
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_cons.h>
|
|
|
|
#define Color_RANGE Color_BBLUE
|
|
#define Color_INSERT Color_BGREEN
|
|
#define Color_DELETE Color_BRED
|
|
#define Color_BGINSERT "\x1b[48;5;22m"
|
|
#define Color_BGDELETE "\x1b[48;5;52m"
|
|
|
|
#define RZ_DIFF_DEFAULT_N_GROUPS_BYTES 8
|
|
|
|
#define FAST_MOD2(x, y) ((x) & (y - 1))
|
|
#define FAST_MOD64(x) FAST_MOD2(x, 64)
|
|
#define DIFF_COLOR(prefix) (prefix == '+' ? Color_INSERT : (prefix == '-' ? Color_DELETE : ""))
|
|
#define DIFF_BGCOLOR(prefix) (prefix == '+' ? Color_BGINSERT : (prefix == '-' ? Color_BGDELETE : ""))
|
|
|
|
static inline ut32 count_newlines(RzDiff *diff, const void *array, st32 beg, st32 end) {
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodStringify stringify = diff->methods.stringify;
|
|
int len = 0;
|
|
ut32 count = 0;
|
|
const char *p;
|
|
const void *elem;
|
|
RzStrBuf tmp;
|
|
|
|
for (st32 i = beg; i < end; ++i) {
|
|
rz_strbuf_init(&tmp);
|
|
elem = elem_at(array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
count++;
|
|
}
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static inline void diff_unified_append_ranges(RzList /*<RzDiffOp *>*/ *opcodes, RzStrBuf *sb, bool color) {
|
|
const char *color_beg = color ? Color_RANGE : "";
|
|
const char *color_end = color ? Color_RESET : "";
|
|
|
|
RzDiffOp *first = rz_list_first_val(opcodes);
|
|
RzDiffOp *last = rz_list_last_val(opcodes);
|
|
st32 a_len = last->a_end - first->a_beg;
|
|
st32 b_len = last->b_end - first->b_beg;
|
|
|
|
rz_strbuf_appendf(sb, "%s@@ -%d,%d +%d,%d @@%s\n", color_beg, first->a_beg + 1, a_len, first->b_beg + 1, b_len, color_end);
|
|
}
|
|
|
|
static inline void diff_unified_json_ranges(RzList /*<RzDiffOp *>*/ *opcodes, PJ *pj) {
|
|
RzDiffOp *first = rz_list_first_val(opcodes);
|
|
RzDiffOp *last = rz_list_last_val(opcodes);
|
|
st32 a_len = last->a_end - first->a_beg;
|
|
st32 b_len = last->b_end - first->b_beg;
|
|
|
|
pj_ka(pj, "from");
|
|
pj_N(pj, first->a_beg + 1);
|
|
pj_N(pj, a_len);
|
|
pj_end(pj);
|
|
|
|
pj_ka(pj, "to");
|
|
pj_N(pj, first->b_beg + 1);
|
|
pj_N(pj, b_len);
|
|
pj_end(pj);
|
|
}
|
|
|
|
static inline void diff_unified_append_data(RzDiff *diff, const void *array, st32 beg, st32 end, RzStrBuf *sb, char prefix, bool color) {
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodStringify stringify = diff->methods.stringify;
|
|
int len = 0;
|
|
ut32 count = 0;
|
|
const char *p;
|
|
const void *elem;
|
|
RzStrBuf tmp;
|
|
bool newline = false;
|
|
bool is_bytes = DIFF_IS_BYTES_METHOD(diff->methods);
|
|
|
|
if (beg < 0) {
|
|
beg = 0;
|
|
}
|
|
|
|
const char *bcol = color ? DIFF_COLOR(prefix) : "";
|
|
const char *ecol = color ? (Color_RESET) : "";
|
|
|
|
rz_strbuf_appendf(sb, "%s%c", bcol, prefix);
|
|
for (st32 i = beg; i < end; ++i) {
|
|
if (newline || (is_bytes && count > 0 && !FAST_MOD64(count))) {
|
|
rz_strbuf_appendf(sb, "%s\n%s%c", ecol, bcol, prefix);
|
|
newline = false;
|
|
}
|
|
rz_strbuf_init(&tmp);
|
|
elem = elem_at(array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
count += len;
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
len--;
|
|
newline = true;
|
|
}
|
|
rz_strbuf_append_n(sb, p, len);
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
rz_strbuf_appendf(sb, "%s\n", ecol);
|
|
}
|
|
|
|
// Assumes that color is true, diffing lines, op->type is RZ_DIFF_OP_REPLACE
|
|
// and that the number of inserted lines is equal to the number of deleted
|
|
// lines.
|
|
static inline void diff_unified_lines_hl(RzDiff *diff, RzDiffOp *op, RzStrBuf *sb, char del_prefix, char ins_prefix) {
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodStringify stringify = diff->methods.stringify;
|
|
int len = 0;
|
|
const char *p;
|
|
const void *elem;
|
|
RzStrBuf tmp, tmp2;
|
|
const char *ecol = Color_RESET;
|
|
const char *ebgcol = Color_RESET_BG;
|
|
|
|
const void *a_array = diff->a;
|
|
st32 a_beg = op->a_beg;
|
|
if (a_beg < 0) {
|
|
a_beg = 0;
|
|
}
|
|
st32 a_end = op->a_end;
|
|
|
|
const void *b_array = diff->b;
|
|
st32 b_beg = op->b_beg;
|
|
if (b_beg < 0) {
|
|
b_beg = 0;
|
|
}
|
|
st32 b_end = op->b_end;
|
|
|
|
ut32 num_nl = count_newlines(diff, a_array, a_beg, a_end);
|
|
// + 1 just in case there's no nl at end
|
|
ut32 num_bounds = num_nl + 1;
|
|
st32 *char_bounds = malloc(sizeof(st32) * 2 * num_bounds);
|
|
if (!char_bounds) {
|
|
return;
|
|
}
|
|
for (ut32 i = 0; i < num_bounds; i++) {
|
|
char_bounds[i * 2] = char_bounds[i * 2 + 1] = -1;
|
|
}
|
|
|
|
// Fill char_bounds array
|
|
ut32 bounds_idx = 0;
|
|
bool newline = false;
|
|
st32 i = a_beg;
|
|
st32 j = b_beg;
|
|
for (; i < a_end; ++i) {
|
|
if (newline) {
|
|
bounds_idx++;
|
|
newline = false;
|
|
}
|
|
rz_strbuf_init(&tmp);
|
|
elem = elem_at(a_array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
len--;
|
|
newline = true;
|
|
}
|
|
|
|
int len_b = 0;
|
|
const void *elem_b;
|
|
const char *p_b;
|
|
|
|
for (; j < b_end; ++j) {
|
|
rz_strbuf_init(&tmp2);
|
|
elem_b = elem_at(b_array, j);
|
|
stringify(elem_b, &tmp2);
|
|
len_b = rz_strbuf_length(&tmp2);
|
|
p_b = rz_strbuf_get(&tmp2);
|
|
if (len_b > 0 && p_b[len_b - 1] == '\n') {
|
|
len_b--;
|
|
}
|
|
|
|
if (len && len_b && (p[0] == p_b[0] || p[len - 1] == p_b[len_b - 1])) {
|
|
// Get left bound.
|
|
st32 left = 0;
|
|
for (; left < RZ_MIN(len, len_b) && p[left] == p_b[left]; left++)
|
|
;
|
|
char_bounds[bounds_idx * 2] = left;
|
|
// Get right bound (offset). "- left" chops off
|
|
// the left portion that has already matched.
|
|
st32 right = 0;
|
|
for (; right < RZ_MIN(len, len_b) - left && p[len - 1 - right] == p_b[len_b - 1 - right];
|
|
right++)
|
|
;
|
|
char_bounds[bounds_idx * 2 + 1] = right;
|
|
}
|
|
|
|
rz_strbuf_fini(&tmp2);
|
|
++j;
|
|
break;
|
|
}
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
|
|
// Show deleted lines
|
|
char prefix = del_prefix;
|
|
const char *bcol = DIFF_COLOR(prefix);
|
|
const char *bbgcol = DIFF_BGCOLOR(prefix);
|
|
newline = false;
|
|
bounds_idx = 0;
|
|
|
|
rz_strbuf_appendf(sb, "%s%c", bcol, prefix);
|
|
for (st32 i = a_beg; i < a_end; ++i) {
|
|
if (newline) {
|
|
rz_strbuf_appendf(sb, "%s\n%s%c", ecol, bcol, prefix);
|
|
newline = false;
|
|
bounds_idx++;
|
|
}
|
|
rz_strbuf_init(&tmp);
|
|
elem = elem_at(a_array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
len--;
|
|
newline = true;
|
|
}
|
|
st32 left = char_bounds[bounds_idx * 2];
|
|
st32 right = char_bounds[bounds_idx * 2 + 1];
|
|
if (left < 0 || right < 0 || len - right < left) {
|
|
rz_strbuf_append_n(sb, p, len);
|
|
} else {
|
|
rz_strbuf_append_n(sb, p, left);
|
|
rz_strbuf_append(sb, bbgcol);
|
|
rz_strbuf_append_n(sb, p + left, len - right - left);
|
|
rz_strbuf_append(sb, ebgcol);
|
|
rz_strbuf_append_n(sb, p + len - right, right);
|
|
}
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
rz_strbuf_appendf(sb, "%s\n", ecol);
|
|
|
|
// Show inserted lines
|
|
prefix = ins_prefix;
|
|
bcol = DIFF_COLOR(prefix);
|
|
bbgcol = DIFF_BGCOLOR(prefix);
|
|
newline = false;
|
|
bounds_idx = 0;
|
|
|
|
rz_strbuf_appendf(sb, "%s%c", bcol, prefix);
|
|
for (st32 i = b_beg; i < b_end; ++i) {
|
|
if (newline) {
|
|
rz_strbuf_appendf(sb, "%s\n%s%c", ecol, bcol, prefix);
|
|
newline = false;
|
|
bounds_idx++;
|
|
}
|
|
rz_strbuf_init(&tmp);
|
|
elem = elem_at(b_array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
len--;
|
|
newline = true;
|
|
}
|
|
st32 left = char_bounds[bounds_idx * 2];
|
|
st32 right = char_bounds[bounds_idx * 2 + 1];
|
|
if (left < 0 || right < 0 || len - right < left) {
|
|
rz_strbuf_append_n(sb, p, len);
|
|
} else {
|
|
rz_strbuf_append_n(sb, p, left);
|
|
rz_strbuf_append(sb, bbgcol);
|
|
rz_strbuf_append_n(sb, p + left, len - right - left);
|
|
rz_strbuf_append(sb, ebgcol);
|
|
rz_strbuf_append_n(sb, p + len - right, right);
|
|
}
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
rz_strbuf_appendf(sb, "%s\n", ecol);
|
|
free(char_bounds);
|
|
}
|
|
|
|
static inline void diff_unified_json_data(RzDiff *diff, const void *array, st32 beg, st32 end, PJ *pj, const char *op) {
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodStringify stringify = diff->methods.stringify;
|
|
int len = 0;
|
|
ut32 count = 0;
|
|
const char *p;
|
|
const void *elem;
|
|
RzStrBuf tmp;
|
|
bool newline = false;
|
|
bool is_bytes = DIFF_IS_BYTES_METHOD(diff->methods);
|
|
|
|
if (beg < 0) {
|
|
beg = 0;
|
|
}
|
|
|
|
pj_o(pj);
|
|
pj_ks(pj, "op", op);
|
|
rz_strbuf_init(&tmp);
|
|
for (st32 i = beg; i < end; ++i) {
|
|
if (newline || (is_bytes && count > 0 && !FAST_MOD64(count))) {
|
|
pj_ks(pj, "value", rz_strbuf_get(&tmp));
|
|
pj_end(pj);
|
|
|
|
rz_strbuf_fini(&tmp);
|
|
rz_strbuf_init(&tmp);
|
|
|
|
pj_o(pj);
|
|
pj_ks(pj, "op", op);
|
|
newline = false;
|
|
}
|
|
elem = elem_at(array, i);
|
|
stringify(elem, &tmp);
|
|
len = rz_strbuf_length(&tmp);
|
|
p = rz_strbuf_get(&tmp);
|
|
count += len;
|
|
if (len > 0 && p[len - 1] == '\n') {
|
|
newline = true;
|
|
}
|
|
}
|
|
pj_ks(pj, "value", rz_strbuf_get(&tmp));
|
|
pj_end(pj);
|
|
rz_strbuf_fini(&tmp);
|
|
}
|
|
|
|
/**
|
|
* \brief Produces a diff output with A and B inputs presented immediately adjacent to each other.
|
|
*
|
|
* Produces a diff output with A and B inputs presented immediately adjacent to each other.
|
|
* It begins with range information and is immediately followed with the line additions,
|
|
* line deletions, and any number of the contextual lines.
|
|
* */
|
|
RZ_API RZ_OWN char *rz_diff_unified_text(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time, bool color) {
|
|
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.stringify, NULL);
|
|
RzStrBuf *sb = NULL;
|
|
RzList *groups = NULL;
|
|
RzList *opcodes = NULL;
|
|
RzDiffOp *op = NULL;
|
|
RzListIter *itg = NULL;
|
|
RzListIter *ito = NULL;
|
|
|
|
if (!from) {
|
|
from = "/original";
|
|
}
|
|
if (!to) {
|
|
to = "/modified";
|
|
}
|
|
sb = rz_strbuf_new("");
|
|
if (!sb) {
|
|
RZ_LOG_ERROR("rz_diff_unified: cannot allocate sb\n");
|
|
goto rz_diff_unified_text_fail;
|
|
}
|
|
|
|
if (show_time) {
|
|
char *time = rz_time_date_now_to_string();
|
|
rz_strbuf_appendf(sb, "--- %s %s\n+++ %s %s\n", from, (time ? time : ""), to, (time ? time : ""));
|
|
free(time);
|
|
} else {
|
|
rz_strbuf_appendf(sb, "--- %s\n+++ %s\n", from, to);
|
|
}
|
|
|
|
bool is_lines = DIFF_IS_LINES_METHOD(diff->methods);
|
|
bool is_bytes = DIFF_IS_BYTES_METHOD(diff->methods);
|
|
|
|
groups = rz_diff_opcodes_grouped_new(diff, is_bytes ? RZ_DIFF_DEFAULT_N_GROUPS_BYTES : RZ_DIFF_DEFAULT_N_GROUPS);
|
|
if (!groups) {
|
|
goto rz_diff_unified_text_fail;
|
|
}
|
|
|
|
rz_list_foreach (groups, itg, opcodes) {
|
|
if (rz_list_length(opcodes) < 1) {
|
|
continue;
|
|
}
|
|
diff_unified_append_ranges(opcodes, sb, color);
|
|
rz_list_foreach (opcodes, ito, op) {
|
|
switch (op->type) {
|
|
case RZ_DIFF_OP_DELETE:
|
|
diff_unified_append_data(diff, diff->a, op->a_beg, op->a_end, sb, '-', color);
|
|
break;
|
|
case RZ_DIFF_OP_EQUAL:
|
|
diff_unified_append_data(diff, diff->a, op->a_beg, op->a_end, sb, ' ', color);
|
|
break;
|
|
case RZ_DIFF_OP_INSERT:
|
|
diff_unified_append_data(diff, diff->b, op->b_beg, op->b_end, sb, '+', color);
|
|
break;
|
|
default: // RZ_DIFF_OP_REPLACE
|
|
if (!color || !is_lines ||
|
|
count_newlines(diff, diff->a, op->a_beg, op->a_end) !=
|
|
count_newlines(diff, diff->b, op->b_beg, op->b_end)) {
|
|
diff_unified_append_data(diff, diff->a, op->a_beg, op->a_end, sb, '-', color);
|
|
diff_unified_append_data(diff, diff->b, op->b_beg, op->b_end, sb, '+', color);
|
|
} else {
|
|
diff_unified_lines_hl(diff, op, sb, '-', '+');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rz_list_free(groups);
|
|
return rz_strbuf_drain(sb);
|
|
|
|
rz_diff_unified_text_fail:
|
|
rz_strbuf_free(sb);
|
|
rz_list_free(groups);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Produces a diff output to convert A in B in a JSON format.
|
|
*
|
|
* Produces a diff output with A and B inputs and contains the operations required
|
|
* to convert A in B and the values to remove, insert or keep.
|
|
* */
|
|
RZ_API RZ_OWN PJ *rz_diff_unified_json(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time) {
|
|
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.stringify, NULL);
|
|
PJ *pj = NULL;
|
|
RzList *groups = NULL;
|
|
RzList *opcodes = NULL;
|
|
RzDiffOp *op = NULL;
|
|
RzListIter *itg = NULL;
|
|
RzListIter *ito = NULL;
|
|
|
|
if (!from) {
|
|
from = "/original";
|
|
}
|
|
if (!to) {
|
|
to = "/modified";
|
|
}
|
|
|
|
pj = pj_new();
|
|
if (!pj) {
|
|
RZ_LOG_ERROR("rz_diff_unified: failed to allocate json\n");
|
|
goto rz_diff_unified_json_fail;
|
|
}
|
|
pj_o(pj);
|
|
|
|
if (show_time) {
|
|
char *time = rz_time_date_now_to_string();
|
|
if (!time) {
|
|
RZ_LOG_ERROR("rz_diff_unified: failed to allocate timestamp\n");
|
|
goto rz_diff_unified_json_fail;
|
|
}
|
|
pj_ks(pj, "timestamp", time);
|
|
free(time);
|
|
}
|
|
|
|
pj_ks(pj, "from", from);
|
|
pj_ks(pj, "to", to);
|
|
|
|
groups = rz_diff_opcodes_grouped_new(diff, RZ_DIFF_DEFAULT_N_GROUPS);
|
|
if (!groups) {
|
|
goto rz_diff_unified_json_fail;
|
|
}
|
|
|
|
pj_ka(pj, "diff");
|
|
rz_list_foreach (groups, itg, opcodes) {
|
|
if (rz_list_length(opcodes) < 1) {
|
|
continue;
|
|
}
|
|
pj_o(pj);
|
|
diff_unified_json_ranges(opcodes, pj);
|
|
pj_ka(pj, "ops");
|
|
rz_list_foreach (opcodes, ito, op) {
|
|
if (op->type == RZ_DIFF_OP_EQUAL) {
|
|
diff_unified_json_data(diff, diff->a, op->a_beg, op->a_end, pj, "equal");
|
|
continue;
|
|
}
|
|
if (op->type == RZ_DIFF_OP_DELETE || op->type == RZ_DIFF_OP_REPLACE) {
|
|
diff_unified_json_data(diff, diff->a, op->a_beg, op->a_end, pj, "delete");
|
|
}
|
|
if (op->type == RZ_DIFF_OP_INSERT || op->type == RZ_DIFF_OP_REPLACE) {
|
|
diff_unified_json_data(diff, diff->b, op->b_beg, op->b_end, pj, "insert");
|
|
}
|
|
}
|
|
pj_end(pj);
|
|
pj_end(pj);
|
|
}
|
|
pj_end(pj);
|
|
pj_end(pj);
|
|
|
|
rz_list_free(groups);
|
|
return pj;
|
|
|
|
rz_diff_unified_json_fail:
|
|
pj_free(pj);
|
|
rz_list_free(groups);
|
|
return NULL;
|
|
}
|