Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .c8rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"include": ["lib/**/*.js"],
"exclude": ["/test/**/*.spec.js"],
"extension": [".js"],
"check-coverage": true,
"lines": 85,
"statements": 85,
"functions": 85,
"branches": 85,
"reporter": ["lcov", "text", "text-summary", "html"]
}
15 changes: 0 additions & 15 deletions .eslintrc.js

This file was deleted.

4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
node_modules
!bin/*.js
/lib-cov
test/*.js
coverage.html
.idea
.idea
coverage
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
package-lock.json
doc/
old-coffee-*/
8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 9999
}
304 changes: 156 additions & 148 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion bin/json-diff.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
#!/usr/bin/env node
require('../lib/cli')(process.argv.slice(2));

import { CLI } from '../lib/cli.js';

CLI(process.argv.slice(2));
615 changes: 615 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import js from '@eslint/js';
import prettier from 'eslint-plugin-prettier/recommended';
import globals from 'globals';

export default [
js.configs.recommended,
prettier, // Enforces Prettier formatting rules via ESLint
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.node,
...globals.mocha, // Recognizes mocha primitives like 'describe' and 'it'
},
},
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': 'warn',
},
},
];
59 changes: 30 additions & 29 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const fs = require('fs')
const tty = require('tty')
import fs from 'fs';
import tty from 'tty';

const { diff } = require('./index')
const { colorize } = require('./colorize')
import { diff } from './index';
import { colorize } from './colorize';

module.exports = function (argv) {
export const CLI = function (argv) {
const options = require('dreamopt')(
/* prettier-ignore */
[
'Usage: json-diff [-vCjfoxnskKp] first.json second.json',
' <first.json> Old file #var(file1) #required',
Expand All @@ -25,62 +26,62 @@ module.exports = function (argv) {
' -p, --precision DECIMALS Round all floating point numbers to this number of decimal places prior to comparison'
],
argv
)
);

options.outputKeys = options.outputKeys ? options.outputKeys.split(',') : []
options.excludeKeys = options.excludeKeys ? options.excludeKeys.split(',') : []
options.outputKeys = options.outputKeys ? options.outputKeys.split(',') : [];
options.excludeKeys = options.excludeKeys ? options.excludeKeys.split(',') : [];

if (options.verbose) {
process.stderr.write(`${JSON.stringify(options, null, 2)}\n`)
process.stderr.write(`${JSON.stringify(options, null, 2)}\n`);
}

if (options.verbose) {
process.stderr.write('Loading files...\n')
process.stderr.write('Loading files...\n');
}
const data1 = fs.readFileSync(options.file1, 'utf8')
const data2 = fs.readFileSync(options.file2, 'utf8')
const data1 = fs.readFileSync(options.file1, 'utf8');
const data2 = fs.readFileSync(options.file2, 'utf8');

if (options.verbose) {
process.stderr.write('Parsing old file...\n')
process.stderr.write('Parsing old file...\n');
}
const json1 = JSON.parse(data1)
const json1 = JSON.parse(data1);

if (options.verbose) {
process.stderr.write('Parsing new file...\n')
process.stderr.write('Parsing new file...\n');
}
const json2 = JSON.parse(data2)
const json2 = JSON.parse(data2);

if (options.verbose) {
process.stderr.write('Running diff...\n')
process.stderr.write('Running diff...\n');
}

const result = diff(json1, json2, options)
const result = diff(json1, json2, options);

if (options.color == null) {
options.color = tty.isatty(process.stdout.fd)
options.color = tty.isatty(process.stdout.fd);
}

if (result) {
if (options.raw) {
if (options.verbose) {
process.stderr.write('Serializing JSON output...\n')
process.stderr.write('Serializing JSON output...\n');
}
process.stdout.write(JSON.stringify(result, null, 2) + '\n')
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
} else {
if (options.verbose) {
process.stderr.write('Producing colored output...\n')
process.stderr.write('Producing colored output...\n');
}
const colorizeOptions = {
color: options.color,
maxElisions: options.maxElisions
}
process.stdout.write(colorize(result, colorizeOptions))
maxElisions: options.maxElisions,
};
process.stdout.write(colorize(result, colorizeOptions));
}
process.exit(1)
process.exit(1);
} else {
if (options.verbose) {
process.stderr.write('No diff')
process.stderr.write('No diff');
}
process.exit(0)
process.exit(0);
}
}
};
111 changes: 55 additions & 56 deletions lib/colorize.js
Original file line number Diff line number Diff line change
@@ -1,117 +1,116 @@
const color = require('colors/safe')

const { extendedTypeOf } = require('./util')
import color from 'colors/safe.js';
import { extendedTypeOf } from './util.js';

const Theme = {
' ' (s) { return s },
' ': function (s) {
return s;
},
'+': color.green,
'-': color.red
}
'-': color.red,
};

const subcolorizeToCallback = function (options, key, diff, output, color, indent) {
let subvalue
const prefix = key ? `${key}: ` : ''
const subindent = indent + ' '
let subvalue;
const prefix = key ? `${key}: ` : '';
const subindent = indent + ' ';

const outputElisions = (n) => {
const maxElisions = options.maxElisions === undefined ? Infinity : options.maxElisions
const maxElisions = options.maxElisions === undefined ? Infinity : options.maxElisions;
if (n < maxElisions) {
for (let i = 0; i < n; i++) {
output(' ', subindent + '...')
output(' ', subindent + '...');
}
} else {
output(' ', subindent + `... (${n} entries)`)
output(' ', subindent + `... (${n} entries)`);
}
}
};

switch (extendedTypeOf(diff)) {
case 'object':
if (('__old' in diff) && ('__new' in diff) && (Object.keys(diff).length === 2)) {
subcolorizeToCallback(options, key, diff.__old, output, '-', indent)
return subcolorizeToCallback(options, key, diff.__new, output, '+', indent)
if ('__old' in diff && '__new' in diff && Object.keys(diff).length === 2) {
subcolorizeToCallback(options, key, diff.__old, output, '-', indent);
return subcolorizeToCallback(options, key, diff.__new, output, '+', indent);
} else {
output(color, `${indent}${prefix}{`)
output(color, `${indent}${prefix}{`);
for (const subkey of Object.keys(diff)) {
let m
subvalue = diff[subkey]
let m;
subvalue = diff[subkey];
if ((m = subkey.match(/^(.*)__deleted$/))) {
subcolorizeToCallback(options, m[1], subvalue, output, '-', subindent)
subcolorizeToCallback(options, m[1], subvalue, output, '-', subindent);
} else if ((m = subkey.match(/^(.*)__added$/))) {
subcolorizeToCallback(options, m[1], subvalue, output, '+', subindent)
subcolorizeToCallback(options, m[1], subvalue, output, '+', subindent);
} else {
subcolorizeToCallback(options, subkey, subvalue, output, color, subindent)
subcolorizeToCallback(options, subkey, subvalue, output, color, subindent);
}
}
return output(color, `${indent}}`)
return output(color, `${indent}}`);
}

case 'array': {
output(color, `${indent}${prefix}[`)
output(color, `${indent}${prefix}[`);

let looksLikeDiff = true
let looksLikeDiff = true;
for (const item of diff) {
if ((extendedTypeOf(item) !== 'array') || !((item.length === 2) || ((item.length === 1) && (item[0] === ' '))) || !(typeof (item[0]) === 'string') || (item[0].length !== 1) || !([' ', '-', '+', '~'].includes(item[0]))) {
looksLikeDiff = false
if (extendedTypeOf(item) !== 'array' || !(item.length === 2 || (item.length === 1 && item[0] === ' ')) || !(typeof item[0] === 'string') || item[0].length !== 1 || ![' ', '-', '+', '~'].includes(item[0])) {
looksLikeDiff = false;
}
}

if (looksLikeDiff) {
let op
let elisionCount = 0
let op;
let elisionCount = 0;
for ([op, subvalue] of diff) {
if (op === ' ' && subvalue == null) {
elisionCount++
elisionCount++;
} else {
if (elisionCount > 0) {
outputElisions(elisionCount)
outputElisions(elisionCount);
}
elisionCount = 0
elisionCount = 0;

if (![' ', '~', '+', '-'].includes(op)) {
throw new Error(`Unexpected op '${op}' in ${JSON.stringify(diff, null, 2)}`)
throw new Error(`Unexpected op '${op}' in ${JSON.stringify(diff, null, 2)}`);
}
if (op === '~') { op = ' ' }
subcolorizeToCallback(options, '', subvalue, output, op, subindent)
if (op === '~') op = ' ';

subcolorizeToCallback(options, '', subvalue, output, op, subindent);
}
}
if (elisionCount > 0) {
outputElisions(elisionCount)
outputElisions(elisionCount);
}
} else {
for (subvalue of diff) {
subcolorizeToCallback(options, '', subvalue, output, color, subindent)
subcolorizeToCallback(options, '', subvalue, output, color, subindent);
}
}

return output(color, `${indent}]`)
return output(color, `${indent}]`);
}

default:
if (diff === 0 || diff === null || diff === false || diff === '' || diff) {
return output(color, indent + prefix + JSON.stringify(diff))
return output(color, indent + prefix + JSON.stringify(diff));
}
}
}
};

const colorizeToCallback = (diff, options, output) =>
subcolorizeToCallback(options, '', diff, output, ' ', '')
export const colorizeToCallback = (diff, options, output) => subcolorizeToCallback(options, '', diff, output, ' ', '');

const colorizeToArray = function (diff, options = {}) {
const output = []
colorizeToCallback(diff, options, (color, line) => output.push(`${color}${line}`))
return output
}
export const colorizeToArray = function (diff, options = {}) {
const output = [];
colorizeToCallback(diff, options, (color, line) => output.push(`${color}${line}`));
return output;
};

const colorize = function (diff, options = {}) {
const output = []
export const colorize = function (diff, options = {}) {
const output = [];
colorizeToCallback(diff, options, function (color, line) {
if (options.color != null ? options.color : true) {
return output.push(((options.theme != null ? options.theme[color] : undefined) != null ? (options.theme != null ? options.theme[color] : undefined) : Theme[color])(`${color}${line}`) + '\n')
return output.push(((options.theme != null ? options.theme[color] : undefined) != null ? (options.theme != null ? options.theme[color] : undefined) : Theme[color])(`${color}${line}`) + '\n');
} else {
return output.push(`${color}${line}\n`)
return output.push(`${color}${line}\n`);
}
})
return output.join('')
}

module.exports = { colorize, colorizeToArray, colorizeToCallback }
});
return output.join('');
};
Loading