diff_json 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/diff_json/diff.rb +460 -0
- data/lib/diff_json/html_output.rb +80 -0
- data/lib/diff_json/undefined_value.rb +4 -0
- data/lib/diff_json.rb +5 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 500b28225357427a86dabd6853d94896a4efcaccb9e6f9e8153d2b93535ce183
|
4
|
+
data.tar.gz: de6dae135cebc7bb440239bf40a990c53c1849fc0fd0d2e26f867b083b231050
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8200a8a6996d6ab68ba6544d27172fd22e712aef12f9b6d62570084ca54e416cd495a4f13a73145532530609420b97540b80dbd89c772d95470df08d6dd80b75
|
7
|
+
data.tar.gz: 912663025aa950e5ded31559b2d3106592e58a44f23129421ed11320302e11caa2b274335ac03d7da2f67757fde3e6d03138bc8b420457370b2dc0fc8169d853
|
@@ -0,0 +1,460 @@
|
|
1
|
+
module DiffJson
|
2
|
+
class Diff
|
3
|
+
def initialize(old_json, new_json, **opts)
|
4
|
+
@old_json = old_json
|
5
|
+
@new_json = new_json
|
6
|
+
@opts = {
|
7
|
+
:debug => false,
|
8
|
+
:ignore_object_keys => [],
|
9
|
+
:diff_count_filter => {
|
10
|
+
:only => ['$**'],
|
11
|
+
:except => []
|
12
|
+
}
|
13
|
+
}.merge(opts)
|
14
|
+
@filtered = @opts[:diff_count_filter] != {
|
15
|
+
:only => ['$**'],
|
16
|
+
:except => []
|
17
|
+
}
|
18
|
+
@diff = {
|
19
|
+
:count => {
|
20
|
+
:all => 0,
|
21
|
+
:insert => 0,
|
22
|
+
:update => 0,
|
23
|
+
:delete => 0,
|
24
|
+
:move => 0
|
25
|
+
},
|
26
|
+
:old => [],
|
27
|
+
:new => []
|
28
|
+
}
|
29
|
+
|
30
|
+
calculate
|
31
|
+
end
|
32
|
+
|
33
|
+
def diff
|
34
|
+
return @diff
|
35
|
+
end
|
36
|
+
|
37
|
+
def retrieve_output(output_type = :stdout, **output_opts)
|
38
|
+
case output_type
|
39
|
+
when :stdout
|
40
|
+
when :file
|
41
|
+
when :html
|
42
|
+
html_output = HtmlOutput.new(@diff, **output_opts)
|
43
|
+
return html_output
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def calculate
|
50
|
+
@diff[:old], @diff[:new] = compare_elements(@old_json, @new_json)
|
51
|
+
@calculated = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def compare_elements(old_element, new_element, indent_step = 0, path = '$')
|
55
|
+
debug([
|
56
|
+
'ENTER compare_elements',
|
57
|
+
"Diffing #{path}"
|
58
|
+
])
|
59
|
+
|
60
|
+
old_element_lines, new_element_lines = [], []
|
61
|
+
|
62
|
+
if old_element == new_element
|
63
|
+
debug('Equal elements, no diff required')
|
64
|
+
|
65
|
+
old_element_lines = JSON.pretty_generate(old_element, max_nesting: false, quirks_mode: true).split("\n").map{|el| [' ', "#{indentation(indent_step)}#{el}"]}
|
66
|
+
new_element_lines = JSON.pretty_generate(new_element, max_nesting: false, quirks_mode: true).split("\n").map{|el| [' ', "#{indentation(indent_step)}#{el}"]}
|
67
|
+
else
|
68
|
+
unless value_type(old_element) == value_type(new_element)
|
69
|
+
debug('Opposite type element, no diff required')
|
70
|
+
|
71
|
+
increment_diff_count(path, :insert)
|
72
|
+
increment_diff_count(path, :delete)
|
73
|
+
old_element_lines, new_element_lines = add_blank_lines(
|
74
|
+
JSON.pretty_generate(old_element, max_nesting: false, quirks_mode: true).split("\n").map{|el| ['-', "#{indentation(indent_step)}#{el}"]},
|
75
|
+
JSON.pretty_generate(new_element, max_nesting: false, quirks_mode: true).split("\n").map{|el| ['+', "#{indentation(indent_step)}#{el}"]}
|
76
|
+
)
|
77
|
+
else
|
78
|
+
debug("Found #{value_type(old_element)}, diffing")
|
79
|
+
|
80
|
+
increment_diff_count(path, :update)
|
81
|
+
old_element_lines, new_element_lines = self.send("#{value_type(old_element)}_diff", old_element, new_element, indent_step, path)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
return old_element_lines, new_element_lines
|
86
|
+
end
|
87
|
+
|
88
|
+
def array_diff(old_array, new_array, indent_step, base_path)
|
89
|
+
debug('ENTER array_diff')
|
90
|
+
|
91
|
+
oal, nal = old_array.length, new_array.length
|
92
|
+
sal = oal < nal ? oal : nal
|
93
|
+
lal = oal > nal ? oal : nal
|
94
|
+
old_array_lines, new_array_lines = [[' ', "#{indentation(indent_step)}["]], [[' ', "#{indentation(indent_step)}["]]
|
95
|
+
next_step = indent_step + 1
|
96
|
+
operations = {
|
97
|
+
'none' => [],
|
98
|
+
'arr_add_index' => [],
|
99
|
+
'arr_drop_index' => [],
|
100
|
+
'arr_send_move' => [],
|
101
|
+
'arr_receive_move' => []
|
102
|
+
}
|
103
|
+
|
104
|
+
# Find indices that were added or dropped, if any
|
105
|
+
if oal < nal
|
106
|
+
operations['arr_add_index'] += (oal..(nal - 1)).to_a
|
107
|
+
elsif oal > nal
|
108
|
+
operations['arr_drop_index'] += (nal..(oal - 1)).to_a
|
109
|
+
end
|
110
|
+
|
111
|
+
# Find 'none' and 'move_value' operations
|
112
|
+
(old_array | new_array).each do |v|
|
113
|
+
# For a given value, find all indices of each array that corresponds
|
114
|
+
old_indices, new_indices = array_indices(old_array, v), array_indices(new_array, v)
|
115
|
+
# Same index, same value, no diff necessary
|
116
|
+
operations['none'] += (old_indices & new_indices)
|
117
|
+
|
118
|
+
# Pull the skipped indices before calculating movements
|
119
|
+
old_indices -= operations['none']
|
120
|
+
new_indices -= operations['none']
|
121
|
+
|
122
|
+
# Find values that were moved from one index to another
|
123
|
+
if !old_indices.empty? and !new_indices.empty?
|
124
|
+
max_moves = old_indices.length < new_indices.length ? old_indices.length : new_indices.length
|
125
|
+
possible_moves = []
|
126
|
+
# Make pairs of possible moves
|
127
|
+
old_indices.each do |oi|
|
128
|
+
new_indices.each do |ni|
|
129
|
+
possible_moves << [(oi - ni).abs, [oi, ni]]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
# For the sake of simplicity, we'll arbitrarily decide to use the shortest moves
|
133
|
+
possible_moves.sort!{|x,y| x[0] <=> y[0]}
|
134
|
+
# Take the first (max_moves) moves and add their operations
|
135
|
+
possible_moves[0..(max_moves - 1)].each do |move|
|
136
|
+
operations['arr_send_move'] << move[1][0]
|
137
|
+
operations['arr_receive_move'] << move[1][1]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add base diff for each index
|
143
|
+
(0..(lal - 1)).each do |i|
|
144
|
+
debug("PROCESS INDEX #{i}")
|
145
|
+
|
146
|
+
item_path = "#{base_path}[#{i}]"
|
147
|
+
old_item_lines, new_item_lines = [], []
|
148
|
+
item_diff_operations = []
|
149
|
+
last_loop = (i == (lal - 1))
|
150
|
+
|
151
|
+
# Assign current known operations to each index
|
152
|
+
(operations.keys).each do |operation|
|
153
|
+
if operations[operation].include?(i)
|
154
|
+
item_diff_operations << operation
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Add arr_change_value, arr_add_value, and arr_drop_value operations
|
159
|
+
if item_diff_operations.empty?
|
160
|
+
item_diff_operations << 'arr_change_value'
|
161
|
+
elsif (
|
162
|
+
item_diff_operations.include?('arr_send_move') and
|
163
|
+
!item_diff_operations.include?('arr_receive_move') and
|
164
|
+
!item_diff_operations.include?('arr_drop_index')
|
165
|
+
)
|
166
|
+
item_diff_operations << 'arr_add_value'
|
167
|
+
elsif (
|
168
|
+
!item_diff_operations.include?('arr_send_move') and
|
169
|
+
item_diff_operations.include?('arr_receive_move') and
|
170
|
+
!item_diff_operations.include?('arr_add_index')
|
171
|
+
)
|
172
|
+
item_diff_operations << 'arr_drop_value'
|
173
|
+
end
|
174
|
+
|
175
|
+
# Call compare_elements for sub-elements if necessary
|
176
|
+
if (!(item_diff_operations & ['none', 'arr_change_value']).empty? and
|
177
|
+
is_json_element?(old_array[i]) and is_json_element?(new_array[i])
|
178
|
+
)
|
179
|
+
old_item_lines, new_item_lines = compare_elements(old_array[i], new_array[i], next_step, item_path)
|
180
|
+
else
|
181
|
+
# Grab old and new items
|
182
|
+
# UndefinedValue class is here to represent the difference between explicit null and non-existent
|
183
|
+
old_item = item_diff_operations.include?('arr_add_index') ? UndefinedValue.new : old_array[i]
|
184
|
+
new_item = item_diff_operations.include?('arr_drop_index') ? UndefinedValue.new : new_array[i]
|
185
|
+
|
186
|
+
# Figure out operators for left and right
|
187
|
+
if item_diff_operations.include?('none')
|
188
|
+
old_operator, new_operator = ' '
|
189
|
+
elsif item_diff_operations.include?('arr_change_value')
|
190
|
+
increment_diff_count(item_path, :update)
|
191
|
+
old_operator, new_operator = '-', '+'
|
192
|
+
elsif (item_diff_operations & ['arr_send_move', 'arr_receive_move']).length == 2
|
193
|
+
increment_diff_count(item_path, :move)
|
194
|
+
old_operator, new_operator = 'M', 'M'
|
195
|
+
elsif item_diff_operations.include?('arr_add_value')
|
196
|
+
increment_diff_count(item_path, :insert)
|
197
|
+
old_operator, new_operator = 'M', '+'
|
198
|
+
elsif item_diff_operations.include?('arr_drop_value')
|
199
|
+
increment_diff_count(item_path, :delete)
|
200
|
+
old_operator, new_operator = '-', 'M'
|
201
|
+
elsif item_diff_operations.include?('arr_drop_index')
|
202
|
+
if item_diff_operations.include?('arr_send_move')
|
203
|
+
increment_diff_count(item_path, :move)
|
204
|
+
old_operator, new_operator = 'M', ' '
|
205
|
+
else
|
206
|
+
increment_diff_count(item_path, :delete)
|
207
|
+
old_operator, new_operator = '-', ' '
|
208
|
+
end
|
209
|
+
elsif item_diff_operations.include?('arr_add_index')
|
210
|
+
if item_diff_operations.include?('arr_receive_move')
|
211
|
+
old_operator, new_operator = ' ', 'M'
|
212
|
+
else
|
213
|
+
increment_diff_count(item_path, :insert)
|
214
|
+
old_operator, new_operator = ' ', '+'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Gather lines
|
219
|
+
if old_item.is_a?(UndefinedValue)
|
220
|
+
new_item_lines = JSON.pretty_generate(new_item, max_nesting: false, quirks_mode: true).split("\n").map{|il| [new_operator, "#{indentation(next_step)}#{il}"]}
|
221
|
+
|
222
|
+
(0..(new_item_lines.length - 1)).each do |i|
|
223
|
+
old_item_lines << [' ', '']
|
224
|
+
end
|
225
|
+
else
|
226
|
+
old_item_lines = JSON.pretty_generate(old_item, max_nesting: false, quirks_mode: true).split("\n").map{|il| [old_operator, "#{indentation(next_step)}#{il}"]}
|
227
|
+
end
|
228
|
+
|
229
|
+
if new_item.is_a?(UndefinedValue)
|
230
|
+
(0..(old_item_lines.length - 1)).each do |i|
|
231
|
+
new_item_lines << [' ', '']
|
232
|
+
end
|
233
|
+
else
|
234
|
+
new_item_lines = JSON.pretty_generate(new_item, max_nesting: false, quirks_mode: true).split("\n").map{|il| [new_operator, "#{indentation(next_step)}#{il}"]}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
unless old_item_lines.empty?
|
239
|
+
old_item_lines.last[1] = "#{old_item_lines.last[1]}," if !last_loop and (old_item_lines.last[1].match(/[^\s]/))
|
240
|
+
end
|
241
|
+
unless new_item_lines.empty?
|
242
|
+
new_item_lines.last[1] = "#{new_item_lines.last[1]}," if !last_loop and (new_item_lines.last[1].match(/[^\s]/))
|
243
|
+
end
|
244
|
+
|
245
|
+
old_item_lines, new_item_lines = add_blank_lines(old_item_lines, new_item_lines)
|
246
|
+
|
247
|
+
old_array_lines += old_item_lines
|
248
|
+
new_array_lines += new_item_lines
|
249
|
+
end
|
250
|
+
|
251
|
+
old_array_lines << [' ', "#{indentation(indent_step)}]"]
|
252
|
+
new_array_lines << [' ', "#{indentation(indent_step)}]"]
|
253
|
+
|
254
|
+
return old_array_lines, new_array_lines
|
255
|
+
end
|
256
|
+
|
257
|
+
def object_diff(old_object, new_object, indent_step, base_path)
|
258
|
+
debug('ENTER object_diff')
|
259
|
+
|
260
|
+
keys = {
|
261
|
+
'all' => (old_object.keys | new_object.keys),
|
262
|
+
'common' => (old_object.keys & new_object.keys),
|
263
|
+
'add' => (new_object.keys - old_object.keys),
|
264
|
+
'drop' => (old_object.keys - new_object.keys)
|
265
|
+
}
|
266
|
+
old_object_lines, new_object_lines = [[' ', "#{indentation(indent_step)}{"]], [[' ', "#{indentation(indent_step)}{"]]
|
267
|
+
next_step = indent_step + 1
|
268
|
+
|
269
|
+
# For objects, we're taking a much simpler approach, so no movements
|
270
|
+
keys['all'].each do |k|
|
271
|
+
debug("PROCESS KEY #{k}")
|
272
|
+
|
273
|
+
item_path = "#{base_path}{#{k}}"
|
274
|
+
key_string = "#{k}: "
|
275
|
+
old_item_lines, new_item_lines = [], []
|
276
|
+
last_loop = (k == keys['all'].last)
|
277
|
+
|
278
|
+
if keys['common'].include?(k)
|
279
|
+
if is_json_element?(old_object[k]) and is_json_element?(new_object[k]) and !@opts[:ignore_object_keys].include?(k)
|
280
|
+
old_item_lines, new_item_lines = compare_elements(old_object[k], new_object[k], next_step, item_path)
|
281
|
+
else
|
282
|
+
if old_object[k] == new_object[k] or @opts[:ignore_object_keys].include?(k)
|
283
|
+
old_item_lines = JSON.pretty_generate(old_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| [' ', "#{indentation(next_step)}#{il}"]}
|
284
|
+
new_item_lines = JSON.pretty_generate(new_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| [' ', "#{indentation(next_step)}#{il}"]}
|
285
|
+
else
|
286
|
+
increment_diff_count(item_path, :update)
|
287
|
+
old_item_lines = JSON.pretty_generate(old_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| ['-', "#{indentation(next_step)}#{il}"]}
|
288
|
+
new_item_lines = JSON.pretty_generate(new_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| ['+', "#{indentation(next_step)}#{il}"]}
|
289
|
+
end
|
290
|
+
end
|
291
|
+
else
|
292
|
+
if keys['drop'].include?(k)
|
293
|
+
increment_diff_count(item_path, :delete) unless @opts[:ignore_object_keys].include?(k)
|
294
|
+
old_item_lines = JSON.pretty_generate(old_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| [@opts[:ignore_object_keys].include?(k) ? ' ' : '-', "#{indentation(next_step)}#{il}"]}
|
295
|
+
new_item_lines = []
|
296
|
+
|
297
|
+
(0..(old_item_lines.length - 1)).each do |i|
|
298
|
+
new_item_lines << [' ', '']
|
299
|
+
end
|
300
|
+
elsif keys['add'].include?(k)
|
301
|
+
increment_diff_count(item_path, :insert) unless @opts[:ignore_object_keys].include?(k)
|
302
|
+
new_item_lines = JSON.pretty_generate(new_object[k], max_nesting: false, quirks_mode: true).split("\n").map!{|il| [@opts[:ignore_object_keys].include?(k) ? ' ' : '+', "#{indentation(next_step)}#{il}"]}
|
303
|
+
old_item_lines = []
|
304
|
+
|
305
|
+
(0..(new_item_lines.length - 1)).each do |i|
|
306
|
+
old_item_lines << [' ', '']
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
unless old_item_lines.empty?
|
312
|
+
old_item_lines[0][1].gsub!(/^(?<spaces>\s+)(?<content>.+)$/, "\\k<spaces>#{key_string}\\k<content>")
|
313
|
+
old_item_lines.last[1] = "#{old_item_lines.last[1]}," if !last_loop and (old_item_lines.last[1].match(/[^\s]/))
|
314
|
+
end
|
315
|
+
unless new_item_lines.empty?
|
316
|
+
new_item_lines[0][1].gsub!(/^(?<spaces>\s+)(?<content>.+)$/, "\\k<spaces>#{key_string}\\k<content>")
|
317
|
+
new_item_lines.last[1] = "#{new_item_lines.last[1]}," if !last_loop and (new_item_lines.last[1].match(/[^\s]/))
|
318
|
+
end
|
319
|
+
|
320
|
+
old_item_lines, new_item_lines = add_blank_lines(old_item_lines, new_item_lines)
|
321
|
+
|
322
|
+
old_object_lines += old_item_lines
|
323
|
+
new_object_lines += new_item_lines
|
324
|
+
end
|
325
|
+
|
326
|
+
old_object_lines << [' ', "#{indentation(indent_step)}}"]
|
327
|
+
new_object_lines << [' ', "#{indentation(indent_step)}}"]
|
328
|
+
|
329
|
+
return old_object_lines, new_object_lines
|
330
|
+
end
|
331
|
+
|
332
|
+
def debug(message)
|
333
|
+
puts message if @opts[:debug]
|
334
|
+
end
|
335
|
+
|
336
|
+
def array_indices(array, value)
|
337
|
+
indices = []
|
338
|
+
|
339
|
+
array.each_with_index do |av,i|
|
340
|
+
indices << i if av == value
|
341
|
+
end
|
342
|
+
|
343
|
+
return indices
|
344
|
+
end
|
345
|
+
|
346
|
+
def is_json_element?(object)
|
347
|
+
return true if ['array', 'object'].include?(value_type(object))
|
348
|
+
end
|
349
|
+
|
350
|
+
def value_type(element)
|
351
|
+
case class_name = element.class.name
|
352
|
+
when 'Hash'
|
353
|
+
return 'object'
|
354
|
+
when 'NilClass'
|
355
|
+
return 'null'
|
356
|
+
when 'TrueClass', 'FalseClass'
|
357
|
+
return 'boolean'
|
358
|
+
else
|
359
|
+
return class_name.downcase
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def indentation(step)
|
364
|
+
step = 0 if step < 0
|
365
|
+
' ' * step
|
366
|
+
end
|
367
|
+
|
368
|
+
def add_blank_lines(left_lines, right_lines)
|
369
|
+
if left_lines.length < right_lines.length
|
370
|
+
(1..(right_lines.length - left_lines.length)).each do
|
371
|
+
left_lines << [' ', '']
|
372
|
+
end
|
373
|
+
elsif left_lines.length > right_lines.length
|
374
|
+
(1..(left_lines.length - right_lines.length)).each do
|
375
|
+
right_lines << [' ', '']
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
return left_lines, right_lines
|
380
|
+
end
|
381
|
+
|
382
|
+
def increment_diff_count(path, operation)
|
383
|
+
unless @filtered
|
384
|
+
@diff[:count][operation] += 1
|
385
|
+
else
|
386
|
+
do_count = false
|
387
|
+
|
388
|
+
# Any path prefixes in `only` that match?
|
389
|
+
if (
|
390
|
+
@opts[:diff_count_filter].key?(:only) and
|
391
|
+
@opts[:diff_count_filter][:only].is_a?(Array)
|
392
|
+
)
|
393
|
+
@opts[:diff_count_filter][:only].each do |only_path|
|
394
|
+
only_path_prefix = only_path.gsub(/\*/, '')
|
395
|
+
only_path_wildcard = only_path.gsub(/[^\*]/, '')
|
396
|
+
|
397
|
+
if path.include?(only_path_prefix)
|
398
|
+
path_remainder = path.gsub(only_path_prefix, '').split(/(\]\[|\]\{|\}\[|\}\{)/)
|
399
|
+
|
400
|
+
if (
|
401
|
+
(path_remainder.length == 0 and only_path_wildcard.length == 0) or
|
402
|
+
(path_remainder.length == 1 and only_path_wildcard.length > 0) or
|
403
|
+
(path_remainder.length > 1 and only_path_wildcard.length > 1)
|
404
|
+
)
|
405
|
+
do_count = true
|
406
|
+
break
|
407
|
+
end
|
408
|
+
else
|
409
|
+
next
|
410
|
+
end
|
411
|
+
end
|
412
|
+
else
|
413
|
+
do_count = true
|
414
|
+
end
|
415
|
+
|
416
|
+
# Make sure the specific path is not excluded, if we've established that we should probably include it
|
417
|
+
if (
|
418
|
+
do_count and
|
419
|
+
@opts[:diff_count_filter].key?(:except) and
|
420
|
+
@opts[:diff_count_filter][:except].is_a?(Array)
|
421
|
+
)
|
422
|
+
@opts[:diff_count_filter][:except].each do |except_path|
|
423
|
+
except_path_prefix = except_path.gsub(/\*/, '')
|
424
|
+
except_path_wildcard = except_path.gsub(/[^\*]/, '') || ''
|
425
|
+
|
426
|
+
if path.include?(except_path_prefix)
|
427
|
+
path_remainder = path.gsub(except_path_prefix, '').split(/(\]\[|\]\{|\}\[|\}\{)/)
|
428
|
+
|
429
|
+
if (
|
430
|
+
(path_remainder.length == 0 and except_path_wildcard.length == 0) or
|
431
|
+
(path_remainder.length == 1 and except_path_wildcard.length > 0) or
|
432
|
+
(path_remainder.length > 1 and except_path_wildcard.length > 1)
|
433
|
+
)
|
434
|
+
do_count = false
|
435
|
+
break
|
436
|
+
end
|
437
|
+
else
|
438
|
+
next
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Ensure this operation is allowed for counting
|
444
|
+
if (
|
445
|
+
do_count and
|
446
|
+
@opts[:diff_count_filter].key?(:operations) and
|
447
|
+
@opts[:diff_count_filter][:operations].is_a?(Array)
|
448
|
+
)
|
449
|
+
do_count = false if (
|
450
|
+
!@opts[:diff_count_filter][:operations].empty? and
|
451
|
+
!@opts[:diff_count_filter][:operations].include?(operation)
|
452
|
+
)
|
453
|
+
end
|
454
|
+
|
455
|
+
@diff[:count][:all] += 1 if do_count
|
456
|
+
@diff[:count][operation] += 1 if do_count
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module DiffJson
|
2
|
+
class HtmlOutput
|
3
|
+
|
4
|
+
def initialize(diff, **opts)
|
5
|
+
@diff = diff
|
6
|
+
@opts = {
|
7
|
+
:split => false,
|
8
|
+
:table_id_prefix => 'diff_json_view_0'
|
9
|
+
}.merge(opts)
|
10
|
+
|
11
|
+
calculate
|
12
|
+
end
|
13
|
+
|
14
|
+
def output
|
15
|
+
return @output
|
16
|
+
end
|
17
|
+
|
18
|
+
def left
|
19
|
+
return @output[:left] if @opts[:split]
|
20
|
+
|
21
|
+
raise 'Method `#left` is only available for split output'
|
22
|
+
end
|
23
|
+
|
24
|
+
def right
|
25
|
+
return @output[:right] if @opts[:split]
|
26
|
+
|
27
|
+
raise 'Method `#right` is only available for split output'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def calculate
|
33
|
+
if @opts[:split]
|
34
|
+
@output = {
|
35
|
+
:left => "<table id=\"#{@opts[:table_id_prefix]}_left\" class=\"diff-json-split-view-left\">\n",
|
36
|
+
:right => "<table id=\"#{@opts[:table_id_prefix]}_right\" class=\"diff-json-split-view-right\">\n"
|
37
|
+
}
|
38
|
+
|
39
|
+
(0..(@diff[:old].length - 1)).each do |i|
|
40
|
+
@output[:left] += "<tr class=\"diff-json-view-line\">\n"
|
41
|
+
@output[:left] += "<td class=\"diff-json-view-line-operator\">#{@diff[:old][i][0].gsub(/\s/, ' ')}</td>\n"
|
42
|
+
@output[:left] += "<td class=\"diff-json-view-line-content #{content_highlight_class(:left, @diff[:old][i][0])}\">#{@diff[:old][i][1].gsub(/\s/, ' ')}</td>\n"
|
43
|
+
@output[:left] += "</tr>\n"
|
44
|
+
@output[:right] += "<tr class=\"diff-json-view-line\">\n"
|
45
|
+
@output[:right] += "<td class=\"diff-json-view-line-operator\">#{@diff[:new][i][0].gsub(/\s/, ' ')}</td>\n"
|
46
|
+
@output[:right] += "<td class=\"diff-json-view-line-content #{content_highlight_class(:right, @diff[:new][i][0])}\">#{@diff[:new][i][1].gsub(/\s/, ' ')}</td>\n"
|
47
|
+
@output[:right] += "</tr>\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
@output[:left] += "</table>\n"
|
51
|
+
@output[:right] += "</table>\n"
|
52
|
+
else
|
53
|
+
@output = "<table id=\"#{@opts[:table_id_prefix]}_full\" class=\"diff-json-view\">\n"
|
54
|
+
|
55
|
+
(0..(@diff[:old].length - 1)).each do |i|
|
56
|
+
@output += "<tr class=\"diff-json-view-line\">\n"
|
57
|
+
@output += "<td class=\"diff-json-view-line-operator\">#{@diff[:old][i][0].gsub(/\s/, ' ')}</td>\n"
|
58
|
+
@output += "<td class=\"diff-json-view-line-content #{content_highlight_class(:left, @diff[:old][i][0])}\">#{@diff[:old][i][1].gsub(/\s/, ' ')}</td>\n"
|
59
|
+
@output += "<td class=\"diff-json-view-line-operator\">#{@diff[:new][i][0].gsub(/\s/, ' ')}</td>\n"
|
60
|
+
@output += "<td class=\"diff-json-view-line-content #{content_highlight_class(:right, @diff[:new][i][0])}\">#{@diff[:new][i][1].gsub(/\s/, ' ')}</td>\n"
|
61
|
+
@output += "</tr>\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
@output += "</table>\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def content_highlight_class(side, operator)
|
69
|
+
if operator == '-'
|
70
|
+
return 'diff-json-content-del'
|
71
|
+
elsif operator == '+'
|
72
|
+
return 'diff-json-content-ins'
|
73
|
+
elsif operator == 'M'
|
74
|
+
return side == :left ? 'diff-json-content-del' : 'diff-json-content-ins'
|
75
|
+
else
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/diff_json.rb
ADDED
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: diff_json
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh MacLachlan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Diffs two JSON objects and returns a left/right diff view, similar to
|
14
|
+
the command line `diff` utility
|
15
|
+
email: josh.t.maclachlan@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/diff_json.rb
|
21
|
+
- lib/diff_json/diff.rb
|
22
|
+
- lib/diff_json/html_output.rb
|
23
|
+
- lib/diff_json/undefined_value.rb
|
24
|
+
homepage: https://github.com/jtmaclachlan/diff_json
|
25
|
+
licenses:
|
26
|
+
- GPL-2
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.7.6
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Diffs two JSON objects and returns a left/right diff view, similar to the
|
48
|
+
command line `diff` utility
|
49
|
+
test_files: []
|