diff --git a/README.md b/README.md index 703f60f..ad3b02c 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,12 @@ Detailed: second.json New file General options: - -v, --verbose Output progress info - -C, --[no-]color Colored output - -j, --raw-json Display raw JSON encoding of the diff - -k, --keys-only Compare only the keys, ignore the differences in values - -h, --help Display this usage information + -b, --bigNumberSupport Handle large numbers as bignumber.js objects to ensure correct diffs for them" + -v, --verbose Output progress info + -C, --[no-]color Colored output + -j, --raw-json Display raw JSON encoding of the diff + -k, --keys-only Compare only the keys, ignore the differences in values + -h, --help Display this usage information In javascript (ES5): diff --git a/lib/cli.coffee b/lib/cli.coffee index 05089a7..2dd611a 100644 --- a/lib/cli.coffee +++ b/lib/cli.coffee @@ -1,4 +1,5 @@ fs = require 'fs' +JSONbig = require 'true-json-bigint' tty = require 'tty' { diff } = require './index' @@ -13,6 +14,7 @@ module.exports = (argv) -> " second.json New file #var(file2) #required" "General options:" + " -b, --bigNumberSupport Handle large numbers as bignumber.js objects to ensure correct diffs for them" " -v, --verbose Output progress info" " -C, --[no-]color Colored output" " -j, --raw-json Display raw JSON encoding of the diff #var(raw)" @@ -26,9 +28,9 @@ module.exports = (argv) -> data2 = fs.readFileSync(options.file2, 'utf8') process.stderr.write "Parsing old file...\n" if options.verbose - json1 = JSON.parse(data1) + json1 = if options.bigNumberSupport then JSONbig.parse(data1) else JSON.parse(data1) process.stderr.write "Parsing new file...\n" if options.verbose - json2 = JSON.parse(data2) + json2 = if options.bigNumberSupport then JSONbig.parse(data2) else JSON.parse(data2) process.stderr.write "Running diff...\n" if options.verbose result = diff(json1, json2, options) @@ -38,10 +40,10 @@ module.exports = (argv) -> if result if options.raw process.stderr.write "Serializing JSON output...\n" if options.verbose - process.stdout.write JSON.stringify(result, null, 2) + process.stdout.write if options.bigNumberSupport then JSONbig.stringify(result, null, 2) else JSON.stringify(result, null, 2) else process.stderr.write "Producing colored output...\n" if options.verbose - process.stdout.write colorize(result, color: options.color) + process.stdout.write colorize(result, {color: options.color, bigNumberSupport: if options.bigNumberSupport then true else false }) else process.stderr.write "No diff" if options.verbose diff --git a/lib/colorize.coffee b/lib/colorize.coffee index 3c51eb3..3c6bd3c 100644 --- a/lib/colorize.coffee +++ b/lib/colorize.coffee @@ -1,4 +1,5 @@ color = require 'cli-color' +JSONbig = require 'true-json-bigint' { extendedTypeOf } = require './util' @@ -8,24 +9,23 @@ Theme = '-': color.red -subcolorizeToCallback = (key, diff, output, color, indent) -> +subcolorizeToCallback = (key, diff, output, color, indent, options) -> prefix = if key then "#{key}: " else '' subindent = indent + ' ' - - switch extendedTypeOf(diff) + switch extendedTypeOf(diff, options.bigNumberSupport) when 'object' if ('__old' of diff) and ('__new' of diff) and (Object.keys(diff).length is 2) - subcolorizeToCallback(key, diff.__old, output, '-', indent) - subcolorizeToCallback(key, diff.__new, output, '+', indent) + subcolorizeToCallback(key, diff.__old, output, '-', indent, options) + subcolorizeToCallback(key, diff.__new, output, '+', indent, options) else output color, "#{indent}#{prefix}{" for own subkey, subvalue of diff if m = subkey.match /^(.*)__deleted$/ - subcolorizeToCallback(m[1], subvalue, output, '-', subindent) + subcolorizeToCallback(m[1], subvalue, output, '-', subindent, options) else if m = subkey.match /^(.*)__added$/ - subcolorizeToCallback(m[1], subvalue, output, '+', subindent) + subcolorizeToCallback(m[1], subvalue, output, '+', subindent, options) else - subcolorizeToCallback(subkey, subvalue, output, color, subindent) + subcolorizeToCallback(subkey, subvalue, output, color, subindent, options) output color, "#{indent}}" when 'array' @@ -33,7 +33,7 @@ subcolorizeToCallback = (key, diff, output, color, indent) -> looksLikeDiff = yes for item in diff - if (extendedTypeOf(item) isnt 'array') or !((item.length is 2) or ((item.length is 1) and (item[0] is ' '))) or !(typeof(item[0]) is 'string') or item[0].length != 1 or !(item[0] in [' ', '-', '+', '~']) + if (extendedTypeOf(item, options.bigNumberSupport) isnt 'array') or !((item.length is 2) or ((item.length is 1) and (item[0] is ' '))) or !(typeof(item[0]) is 'string') or item[0].length != 1 or !(item[0] in [' ', '-', '+', '~']) looksLikeDiff = no if looksLikeDiff @@ -42,34 +42,36 @@ subcolorizeToCallback = (key, diff, output, color, indent) -> output(' ', subindent + '...') else unless op in [' ', '~', '+', '-'] - throw new Error("Unexpected op '#{op}' in #{JSON.stringify(diff, null, 2)}") + stringifiedJSON = if options.bigNumberSupport then JSONbig.stringify(diff, null, 2) else JSON.stringify(diff, null, 2) + throw new Error("Unexpected op '#{op}' in #{stringifiedJSON}") op = ' ' if op is '~' - subcolorizeToCallback('', subvalue, output, op, subindent) + subcolorizeToCallback('', subvalue, output, op, subindent, options) else for subvalue in diff - subcolorizeToCallback('', subvalue, output, color, subindent) + subcolorizeToCallback('', subvalue, output, color, subindent, options) output color, "#{indent}]" else if diff == 0 or diff - output(color, indent + prefix + JSON.stringify(diff)) + stringifiedJSON = if options.bigNumberSupport then JSONbig.stringify(diff, null, 2) else JSON.stringify(diff, null, 2) + output(color, indent + prefix + stringifiedJSON) -colorizeToCallback = (diff, output) -> - subcolorizeToCallback('', diff, output, ' ', '') +colorizeToCallback = (diff, options, output) -> + subcolorizeToCallback('', diff, output, ' ', '', options) -colorizeToArray = (diff) -> +colorizeToArray = (diff, options = {}) -> output = [] - colorizeToCallback(diff, (color, line) -> output.push "#{color}#{line}") + colorizeToCallback(diff, options, (color, line) -> output.push "#{color}#{line}") return output colorize = (diff, options={}) -> output = [] - colorizeToCallback diff, (color, line) -> + colorizeToCallback diff, options, (color, line) -> if options.color ? yes output.push (options.theme?[color] ? Theme[color])("#{color}#{line}") + "\n" else diff --git a/lib/index.coffee b/lib/index.coffee index 77c8696..10da926 100644 --- a/lib/index.coffee +++ b/lib/index.coffee @@ -1,3 +1,5 @@ +BigNumber = require 'bignumber.js' + { SequenceMatcher } = require 'difflib' { extendedTypeOf } = require './util' { colorize } = require './colorize' @@ -36,14 +38,14 @@ objectDiff = (obj1, obj2, options = {}) -> return [score, result] -findMatchingObject = (item, index, fuzzyOriginals) -> +findMatchingObject = (item, index, fuzzyOriginals, options) -> # console.log "findMatchingObject: " + JSON.stringify({item, fuzzyOriginals}, null, 2) bestMatch = null matchIndex = 0 for own key, candidate of fuzzyOriginals when key isnt '__next' indexDistance = Math.abs(matchIndex - index) - if extendedTypeOf(item) == extendedTypeOf(candidate) + if extendedTypeOf(item, options.bigNumberSupport) == extendedTypeOf(candidate, options.bigNumberSupport) score = diffScore(item, candidate) if !bestMatch || score > bestMatch.score || (score == bestMatch.score && indexDistance < bestMatch.indexDistance) bestMatch = { score, key, indexDistance } @@ -53,11 +55,11 @@ findMatchingObject = (item, index, fuzzyOriginals) -> bestMatch -scalarize = (array, originals, fuzzyOriginals) -> +scalarize = (array, originals, fuzzyOriginals, options) -> for item, index in array if isScalar item item - else if fuzzyOriginals && (bestMatch = findMatchingObject(item, index, fuzzyOriginals)) && bestMatch.score > 40 && !originals[bestMatch.key]? + else if fuzzyOriginals && (bestMatch = findMatchingObject(item, index, fuzzyOriginals, options)) && bestMatch.score > 40 && !originals[bestMatch.key]? originals[bestMatch.key] = item bestMatch.key else @@ -77,9 +79,9 @@ descalarize = (item, originals) -> arrayDiff = (obj1, obj2, options = {}) -> originals1 = { __next: 1 } - seq1 = scalarize(obj1, originals1) + seq1 = scalarize(obj1, originals1, undefined, options) originals2 = { __next: originals1.__next } - seq2 = scalarize(obj2, originals2, originals1) + seq2 = scalarize(obj2, originals2, originals1, options) opcodes = new SequenceMatcher(null, seq1, seq2).getOpcodes() @@ -146,8 +148,8 @@ arrayDiff = (obj1, obj2, options = {}) -> diffWithScore = (obj1, obj2, options = {}) -> - type1 = extendedTypeOf obj1 - type2 = extendedTypeOf obj2 + type1 = extendedTypeOf(obj1, options.bigNumberSupport) + type2 = extendedTypeOf(obj2, options.bigNumberSupport) if type1 == type2 switch type1 @@ -157,7 +159,12 @@ diffWithScore = (obj1, obj2, options = {}) -> return arrayDiff(obj1, obj2, options) if !options.keysOnly - if obj1 != obj2 + if options.bigNumberSupport and BigNumber.isBigNumber(obj1) and BigNumber.isBigNumber(obj2) + if !obj1.isEqualTo(obj2) + [0, { __old: obj1, __new: obj2 }] + else + [100, undefined] + else if obj1 != obj2 [0, { __old: obj1, __new: obj2 }] else [100, undefined] @@ -173,6 +180,10 @@ diffScore = (obj1, obj2, options = {}) -> return score diffString = (obj1, obj2, colorizeOptions, diffOptions = {}) -> + if colorizeOptions != undefined and colorizeOptions.bigNumberSupport + diffOptions.bigNumberSupport = true + if diffOptions != undefined and diffOptions.bigNumberSupport + colorizeOptions.bigNumberSupport = true return colorize(diff(obj1, obj2, diffOptions), colorizeOptions) diff --git a/lib/util.coffee b/lib/util.coffee index a4860c2..086c53d 100644 --- a/lib/util.coffee +++ b/lib/util.coffee @@ -1,10 +1,13 @@ +BigNumber = require 'bignumber.js' -extendedTypeOf = (obj) -> +extendedTypeOf = (obj, bigNumberSupport) -> result = typeof obj if !obj? 'null' else if result is 'object' and obj.constructor is Array 'array' + else if bigNumberSupport and BigNumber.isBigNumber(obj) + 'number' else result diff --git a/package.json b/package.json index e52848d..6c9e4af 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "dependencies": { "cli-color": "~0.1.6", "difflib": "~0.2.1", - "dreamopt": "~0.6.0" + "dreamopt": "~0.6.0", + "true-json-bigint": "^0.4.4" }, "devDependencies": { "coffee-script": "^1.12.7", diff --git a/test/colorize_test.coffee b/test/colorize_test.coffee index 77f01a2..ff9f2ef 100644 --- a/test/colorize_test.coffee +++ b/test/colorize_test.coffee @@ -1,4 +1,5 @@ assert = require 'assert' +BigNumber = require 'bignumber.js' { colorize, colorizeToArray } = require "../#{process.env.JSLIB or 'lib'}/colorize" @@ -44,3 +45,15 @@ describe 'colorize', -> it "should return a string without ANSI escapes on { color: false }", -> assert.equal colorize({ foo: { __old: 42, __new: 10 } }, color: no), " {\n- foo: 42\n+ foo: 10\n }\n" + + +describe 'Big Number Support', -> + + it "should handle a diff with Big Number values", -> + assert.deepEqual colorize({ foo: { __old: BigNumber('3e+5000'), __new: BigNumber('98765432100123456789') } }, {bigNumberSupport: true, color: no}), " {\n- foo: 3e+5000\n+ foo: 98765432100123456789\n }\n" + + it "should handle a diff for an array with Big Number values", -> + assert.deepEqual ['-3e+5000', '+98765432100123456789'], colorizeToArray({ __old: BigNumber('3e+5000'), __new: BigNumber('98765432100123456789') }, bigNumberSupport: true) + + + diff --git a/test/diff_test.coffee b/test/diff_test.coffee index 12133eb..64e8643 100644 --- a/test/diff_test.coffee +++ b/test/diff_test.coffee @@ -1,3 +1,4 @@ +BigNumber = require 'bignumber.js' fs = require 'fs' Path = require 'path' assert = require 'assert' @@ -178,3 +179,24 @@ describe 'diffString', -> it "return an empty string when no diff found", -> assert.equal diffString(a, a), '' + + +describe 'Big Number Support', -> + + it "should handle a diff with different Big Number values", -> + assert.deepEqual { __old: BigNumber('3e+5000'), __new: BigNumber('98765432100123456789') }, diff(BigNumber('3e+5000'), BigNumber('98765432100123456789'), bigNumberSupport: true) + + it "should handle a diff with equal Big Number values", -> + assert.deepEqual undefined, diff(BigNumber('3e+5000'), BigNumber('3e+5000'), bigNumberSupport: true) + + it "should handle a diff for an array with Big Number values", -> + assert.deepEqual [['~', {__old: BigNumber('3e+5000'), __new: BigNumber('98765432100123456789')}], ['~', {__old: BigNumber('3e+6000'), __new: BigNumber('12345678901234567890')}]], diff([BigNumber('3e+5000'), BigNumber('3e+6000')], [BigNumber('98765432100123456789'), BigNumber('12345678901234567890')], bigNumberSupport: true) + + it "should handle a diff when old value is an ordinary number and new value contains a Big Number value", -> + assert.deepEqual { __old: 1, __new: BigNumber('98765432100123456789') }, diff(1, BigNumber('98765432100123456789'), bigNumberSupport: true) + + it "should handle a diff when old value contains a Big Number value and new value is an ordinary number", -> + assert.deepEqual { __old: BigNumber('3e+5000'), __new: 2}, diff(BigNumber('3e+5000'), 2, bigNumberSupport: true) + +it "should handle a diff for an array with Big Number and ordinary integers", -> + assert.deepEqual [['+', 2], ['~', {__old: BigNumber('3e+5000'), __new: BigNumber('12345678901234567890')}],['-', 1]], diff([BigNumber('3e+5000'), 1], [2, BigNumber('12345678901234567890')], bigNumberSupport: true) diff --git a/yarn.lock b/yarn.lock index 816b137..9541a3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +bignumber.js@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" + integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== + cli-color@~0.1.6: version "0.1.7" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-0.1.7.tgz#adc3200fa471cc211b0da7f566b71e98b9d67347" @@ -85,6 +90,13 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +"true-json-bigint@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/true-json-bigint/-/true-json-bigint-0.4.4.tgz#82a2a04a959958ae0eeb819e62481db4717bdf60" + integrity sha512-RE5QJtnrn50WicRYER45GNfnd/CTULiaW3EdtalvAFuMqt/e7XB5mZArEvBRItr8+/KxCpYMMDwcuGoXId/4KA== + dependencies: + bignumber.js "^7.0.0" + wordwrap@>=0.0.2: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"