Skip to content

Commit 7c0a3d4

Browse files
committed
feat: CLI additional flags
CLI flags to specify format and some diff options
1 parent 8d7c63b commit 7c0a3d4

File tree

2 files changed

+107
-27
lines changed

2 files changed

+107
-27
lines changed

packages/jsondiffpatch/bin/jsondiffpatch.js

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,135 @@
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 = typeof flags["--format"] === "string" ? flags["--format"] : "console";
53+
const objectKeys = (flags["--object-keys="] ?? "id,key").split(",")
54+
.map((key) => key.trim());
55+
56+
const jsondiffpatch = create({
57+
objectHash: (obj, index) => {
58+
if (obj && typeof obj === "object") {
59+
for (const key of objectKeys) {
60+
if (key in obj) {
61+
return obj[key];
62+
}
63+
}
64+
}
65+
return index;
66+
},
67+
arrays: {
68+
detectMove: !flags["--no-moves"],
69+
},
70+
omitRemovedValues: !!flags["--omit-removed-values"],
71+
textDiff: {
72+
...(format === "jsonpatch" || !!flags["--no-text-diff"]
73+
? {
74+
// text diff not supported by jsonpatch
75+
minLength: Number.MAX_VALUE,
76+
}
77+
: {}),
78+
},
79+
});
80+
return jsondiffpatch;
81+
}
82+
83+
function printDiff(delta) {
84+
if (flags["--format"] ==="json") {
85+
console.log(JSON.stringify(delta, null, 2));
86+
} else if (flags["--format"] ==="json-compact") {
87+
console.log(JSON.stringify(delta));
88+
} else if (flags["--format"] === "jsonpatch") {
89+
jsonpatchFormatter.log(delta);
90+
} else {
91+
consoleFormatter.log(delta);
92+
}
93+
}
94+
95+
function getJson(path) {
96+
if (/^https?:\/\//i.test(path)) {
97+
// an absolute URL, fetch it
98+
return fetch(path).then((response) => response.json());
99+
}
100+
return JSON.parse(readFileSync(path));
101+
}
102+
103+
const jsondiffpatch = createInstance();
29104

30105
if (files.length !== 2 || flags.includes("--help")) {
31-
console.log(usage);
106+
console.log(usage());
107+
const delta = jsondiffpatch.diff({
108+
property: "before",
109+
list: [
110+
{ id: 1,},
111+
{ id: 2, },
112+
{ id: 3, name: "item removed" },
113+
],
114+
longText: '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+
property: "after",
117+
newProperty: "added",
118+
list: [
119+
{ id: 2, },
120+
{ id: 1, },
121+
{ id: 4, name: "item added" },
122+
],
123+
longText: 'when a text a bit long, diff-match-patch creates a text diff that captures the changes, comparing each characther',
124+
});
125+
printDiff(delta);
32126
} else {
33127
Promise.all([files[0], files[1]].map(getJson)).then(([left, right]) => {
34128
const delta = jsondiffpatch.diff(left, right);
35129
if (delta === undefined) {
36130
process.exit(0);
37131
} 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-
}
132+
printDiff(delta);
45133
// exit code 1 to be consistent with GNU diff
46134
process.exit(1);
47135
}
48136
});
49137
}
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/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)