Skip to content

Commit d9cf9b6

Browse files
authored
Merge pull request #411 from benjamine/fixes
CLI additional flags
2 parents 6ba9df4 + 93e35ac commit d9cf9b6

File tree

3 files changed

+124
-36
lines changed

3 files changed

+124
-36
lines changed

packages/jsondiffpatch/bin/jsondiffpatch.js

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,134 @@
33
import { readFileSync } from "node:fs";
44
import * as consoleFormatter from "../lib/formatters/console.js";
55
import * as jsonpatchFormatter from "../lib/formatters/jsonpatch.js";
6-
import * as jsondiffpatch from "../lib/with-text-diffs.js";
6+
import { create } from "../lib/with-text-diffs.js";
77

8-
const allowedFlags = ["--help", "--format=json", "--format=jsonpatch"];
8+
const allowedFlags = [
9+
"--help",
10+
"--format",
11+
"--omit-removed-values",
12+
"--no-moves",
13+
"--no-text-diff",
14+
"--object-keys",
15+
];
916

1017
const args = process.argv.slice(2);
11-
const flags = [];
18+
const flags = {};
1219
const files = [];
1320
for (const arg of args) {
1421
if (arg.startsWith("--")) {
15-
if (allowedFlags.indexOf(arg) === -1) {
16-
console.error(`unrecognized option: ${arg}`);
22+
const argParts = arg.split("=");
23+
if (allowedFlags.indexOf(argParts[0]) === -1) {
24+
console.error(`unrecognized option: ${argParts[0]}`);
1725
process.exit(2);
1826
}
19-
flags.push(arg);
27+
flags[argParts[0]] = argParts[1] ?? true;
2028
} else {
2129
files.push(arg);
2230
}
2331
}
2432

25-
const usage =
26-
"usage: jsondiffpatch left.json right.json" +
27-
"\n" +
28-
"\n note: http and https URLs are also supported\n";
33+
const usage = () => {
34+
return `usage: jsondiffpatch left.json right.json
35+
note: http and https URLs are also supported
36+
37+
flags:
38+
--format=console (default) print a readable colorized diff
39+
--format=json output the pure JSON diff
40+
--format=json-compact pure JSON diff, no indentation
41+
--format=jsonpatch output JSONPatch (RFC 6902)
42+
43+
--omit-removed-values omits removed values from the diff
44+
--no-moves disable array moves detection
45+
--no-text-diff disable text diffs
46+
--object-keys=... (defaults to: id,key) optional comma-separated properties to match 2 objects between array versions (see objectHash)
47+
48+
example:`;
49+
};
50+
51+
function createInstance() {
52+
const format =
53+
typeof flags["--format"] === "string" ? flags["--format"] : "console";
54+
const objectKeys = (flags["--object-keys="] ?? "id,key")
55+
.split(",")
56+
.map((key) => key.trim());
57+
58+
const jsondiffpatch = create({
59+
objectHash: (obj, index) => {
60+
if (obj && typeof obj === "object") {
61+
for (const key of objectKeys) {
62+
if (key in obj) {
63+
return obj[key];
64+
}
65+
}
66+
}
67+
return index;
68+
},
69+
arrays: {
70+
detectMove: !flags["--no-moves"],
71+
},
72+
omitRemovedValues: !!flags["--omit-removed-values"],
73+
textDiff: {
74+
...(format === "jsonpatch" || !!flags["--no-text-diff"]
75+
? {
76+
// text diff not supported by jsonpatch
77+
minLength: Number.MAX_VALUE,
78+
}
79+
: {}),
80+
},
81+
});
82+
return jsondiffpatch;
83+
}
84+
85+
function printDiff(delta) {
86+
if (flags["--format"] === "json") {
87+
console.log(JSON.stringify(delta, null, 2));
88+
} else if (flags["--format"] === "json-compact") {
89+
console.log(JSON.stringify(delta));
90+
} else if (flags["--format"] === "jsonpatch") {
91+
jsonpatchFormatter.log(delta);
92+
} else {
93+
consoleFormatter.log(delta);
94+
}
95+
}
96+
97+
function getJson(path) {
98+
if (/^https?:\/\//i.test(path)) {
99+
// an absolute URL, fetch it
100+
return fetch(path).then((response) => response.json());
101+
}
102+
return JSON.parse(readFileSync(path));
103+
}
104+
105+
const jsondiffpatch = createInstance();
29106

30107
if (files.length !== 2 || flags.includes("--help")) {
31-
console.log(usage);
108+
console.log(usage());
109+
const delta = jsondiffpatch.diff(
110+
{
111+
property: "before",
112+
list: [{ id: 1 }, { id: 2 }, { id: 3, name: "item removed" }],
113+
longText:
114+
"when a text is very 🦕 long, diff-match-patch is used to create a text diff that only captures the changes, comparing each characther",
115+
},
116+
{
117+
property: "after",
118+
newProperty: "added",
119+
list: [{ id: 2 }, { id: 1 }, { id: 4, name: "item added" }],
120+
longText:
121+
"when a text a bit long, diff-match-patch creates a text diff that captures the changes, comparing each characther",
122+
},
123+
);
124+
printDiff(delta);
32125
} else {
33126
Promise.all([files[0], files[1]].map(getJson)).then(([left, right]) => {
34127
const delta = jsondiffpatch.diff(left, right);
35128
if (delta === undefined) {
36129
process.exit(0);
37130
} else {
38-
if (flags.includes("--format=json")) {
39-
console.log(JSON.stringify(delta, null, 2));
40-
} else if (flags.includes("--format=jsonpatch")) {
41-
jsonpatchFormatter.log(delta);
42-
} else {
43-
consoleFormatter.log(delta);
44-
}
131+
printDiff(delta);
45132
// exit code 1 to be consistent with GNU diff
46133
process.exit(1);
47134
}
48135
});
49136
}
50-
51-
function getJson(path) {
52-
if (/^https?:\/\//i.test(path)) {
53-
// an absolute URL, fetch it
54-
return fetch(path).then((response) => response.json());
55-
}
56-
return JSON.parse(readFileSync(path));
57-
}

packages/jsondiffpatch/src/formatters/base.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ abstract class BaseFormatter<
244244
for (let index = 0; index < keys.length; index++) {
245245
const key = keys[index];
246246
if (key === undefined) continue;
247-
const isLast = index === length - 1;
247+
const isLast = index === keys.length - 1;
248248
fn(
249249
// for object diff, the delta key and left key are the same
250250
key,
@@ -310,8 +310,6 @@ abstract class BaseFormatter<
310310
rightIndex < rightLength ||
311311
`${rightIndex}` in arrayDelta
312312
) {
313-
const isLast =
314-
leftIndex === leftLength - 1 || rightIndex === rightLength - 1;
315313
let hasDelta = false;
316314

317315
const leftIndexKey = `_${leftIndex}` as const;
@@ -333,7 +331,12 @@ abstract class BaseFormatter<
333331
value: leftArray ? leftArray[movedFromIndex] : undefined,
334332
}
335333
: undefined,
336-
isLast && !(rightIndexKey in arrayDelta),
334+
335+
// is this the last key in this delta?
336+
leftIndex === leftLength - 1 &&
337+
rightIndex === rightLength - 1 &&
338+
!(`${rightIndex + 1}` in arrayDelta) &&
339+
!(rightIndexKey in arrayDelta),
337340
);
338341

339342
if (Array.isArray(itemDelta)) {
@@ -357,6 +360,7 @@ abstract class BaseFormatter<
357360
// something happened to the right item at this position
358361
hasDelta = true;
359362
const itemDelta = arrayDelta[rightIndexKey];
363+
const isItemAdded = Array.isArray(itemDelta) && itemDelta.length === 1;
360364
fn(
361365
rightIndexKey,
362366
movedFromIndex ?? leftIndex,
@@ -366,10 +370,14 @@ abstract class BaseFormatter<
366370
value: leftArray ? leftArray[movedFromIndex] : undefined,
367371
}
368372
: undefined,
369-
isLast,
373+
// is this the last key in this delta?
374+
leftIndex === leftLength - 1 &&
375+
rightIndex === rightLength - 1 + (isItemAdded ? 1 : 0) &&
376+
!(`_${leftIndex + 1}` in arrayDelta) &&
377+
!(`${rightIndex + 1}` in arrayDelta),
370378
);
371379

372-
if (Array.isArray(itemDelta) && itemDelta.length === 1) {
380+
if (isItemAdded) {
373381
// added
374382
rightLength++;
375383
rightIndex++;
@@ -398,7 +406,10 @@ abstract class BaseFormatter<
398406
value: leftArray ? leftArray[movedFromIndex] : undefined,
399407
}
400408
: undefined,
401-
isLast,
409+
// is this the last key in this delta?
410+
leftIndex === leftLength - 1 &&
411+
rightIndex === rightLength - 1 &&
412+
!(`${rightIndex + 1}` in arrayDelta),
402413
);
403414
}
404415

@@ -446,9 +457,7 @@ abstract class BaseFormatter<
446457
parseTextDiff(value: string) {
447458
const output = [];
448459
const lines = value.split("\n@@ ");
449-
// for (let i = 0, l = lines.length; i < l; i++) {
450460
for (const line of lines) {
451-
//const line = lines[i];
452461
const lineOutput: {
453462
pieces: LineOutputPiece[];
454463
location?: LineOutputLocation;

packages/jsondiffpatch/src/formatters/console.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class ConsoleFormatter extends BaseFormatter<ConsoleFormatterContext> {
9393
const pieces = line.pieces;
9494
for (const piece of pieces) {
9595
context.pushColor(this.brushes[piece.type]);
96-
context.out(piece.text);
96+
context.out(decodeURI(piece.text));
9797
context.popColor();
9898
}
9999
if (i < lines.length - 1) {

0 commit comments

Comments
 (0)