Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Big Number support #40

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
10 changes: 6 additions & 4 deletions lib/cli.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fs = require 'fs'
JSONbig = require 'true-json-bigint'
tty = require 'tty'

{ diff } = require './index'
Expand All @@ -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)"
Expand All @@ -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)
Expand All @@ -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

Expand Down
38 changes: 20 additions & 18 deletions lib/colorize.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
color = require 'cli-color'
JSONbig = require 'true-json-bigint'

{ extendedTypeOf } = require './util'

Expand All @@ -8,32 +9,31 @@ 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'
output color, "#{indent}#{prefix}["

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
Expand All @@ -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
Expand Down
29 changes: 20 additions & 9 deletions lib/index.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
BigNumber = require 'bignumber.js'

{ SequenceMatcher } = require 'difflib'
{ extendedTypeOf } = require './util'
{ colorize } = require './colorize'
Expand Down Expand Up @@ -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 }
Expand All @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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)


Expand Down
5 changes: 4 additions & 1 deletion lib/util.coffee
Original file line number Diff line number Diff line change
@@ -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

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions test/colorize_test.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
assert = require 'assert'
BigNumber = require 'bignumber.js'

{ colorize, colorizeToArray } = require "../#{process.env.JSLIB or 'lib'}/colorize"

Expand Down Expand Up @@ -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)



22 changes: 22 additions & 0 deletions test/diff_test.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
BigNumber = require 'bignumber.js'
fs = require 'fs'
Path = require 'path'
assert = require 'assert'
Expand Down Expand Up @@ -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)
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -85,6 +90,13 @@ [email protected]:
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"