dyph 0.6.0
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/LICENSE +21 -0
- data/README.md +199 -0
- data/lib/dyph.rb +25 -0
- data/lib/dyph/action.rb +12 -0
- data/lib/dyph/action/add.rb +7 -0
- data/lib/dyph/action/delete.rb +7 -0
- data/lib/dyph/action/no_change.rb +7 -0
- data/lib/dyph/differ.rb +102 -0
- data/lib/dyph/equatable.rb +24 -0
- data/lib/dyph/merge_result.rb +45 -0
- data/lib/dyph/outcome.rb +12 -0
- data/lib/dyph/outcome/conflicted.rb +27 -0
- data/lib/dyph/outcome/resolved.rb +33 -0
- data/lib/dyph/support/assign_action.rb +18 -0
- data/lib/dyph/support/collater.rb +33 -0
- data/lib/dyph/support/diff3.rb +155 -0
- data/lib/dyph/support/merger.rb +169 -0
- data/lib/dyph/support/sanity_check.rb +73 -0
- data/lib/dyph/two_way_differs/heckel_diff.rb +190 -0
- data/lib/dyph/two_way_differs/output_converter.rb +179 -0
- data/lib/dyph/version.rb +3 -0
- metadata +220 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Dyph
|
2
|
+
module Support
|
3
|
+
module SanityCheck
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
7
|
+
def ensure_no_lost_data(left, base, right, final_result)
|
8
|
+
result_word_map = {}
|
9
|
+
final_result.each do |result_block|
|
10
|
+
blocks = case result_block
|
11
|
+
when Outcome::Resolved then result_block.result
|
12
|
+
when Outcome::Conflicted then [result_block.left, result_block.right].flatten
|
13
|
+
else raise "Unknown block type, #{result_block[:type]}"
|
14
|
+
end
|
15
|
+
count_blocks(blocks, result_word_map)
|
16
|
+
end
|
17
|
+
|
18
|
+
left_word_map, base_word_map, right_word_map = [left, base, right].map { |str| count_blocks(str) }
|
19
|
+
|
20
|
+
# new words are words that are in left or right, but not in base
|
21
|
+
new_left_words = subtract_words(left_word_map, base_word_map)
|
22
|
+
new_right_words = subtract_words(right_word_map, base_word_map)
|
23
|
+
|
24
|
+
# now make sure all new words are somewhere in the result
|
25
|
+
missing_new_left_words = subtract_words(new_left_words, result_word_map)
|
26
|
+
missing_new_right_words = subtract_words(new_right_words, result_word_map)
|
27
|
+
|
28
|
+
if missing_new_left_words.any? || missing_new_right_words.any?
|
29
|
+
raise BadMergeException.new(final_result)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# rubocop:enable Metrics/AbcSize
|
33
|
+
|
34
|
+
private
|
35
|
+
def count_blocks(blocks, hash={})
|
36
|
+
blocks.reduce(hash) do |map, block|
|
37
|
+
map[block] ||= 0
|
38
|
+
map[block] += 1
|
39
|
+
map
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def subtract_words(left_map, right_map)
|
44
|
+
remaining_words = {}
|
45
|
+
|
46
|
+
left_map.each do |word, count|
|
47
|
+
count_in_right = right_map[word] || 0
|
48
|
+
|
49
|
+
new_count = count - count_in_right
|
50
|
+
remaining_words[word] = new_count if new_count > 0
|
51
|
+
end
|
52
|
+
|
53
|
+
remaining_words
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class BadMergeException < StandardError
|
58
|
+
attr_accessor :merge_result
|
59
|
+
|
60
|
+
def initialize(merge_result)
|
61
|
+
@merge_result = merge_result
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"<#{self.class}: #{merge_result}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
inspect
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Dyph
|
2
|
+
module TwoWayDiffers
|
3
|
+
|
4
|
+
class HeckelDiff
|
5
|
+
# Algorithm adapted from http://www.rad.upenn.edu/sbia/software/basis/apidoc/v1.2/diff3_8py_source.html
|
6
|
+
|
7
|
+
def self.execute_diff(old_text_array, new_text_array)
|
8
|
+
raise ArgumentError, "Argument is not an array." unless old_text_array.is_a?(Array) && new_text_array.is_a?(Array)
|
9
|
+
diff_result = diff(old_text_array, new_text_array)
|
10
|
+
# convert to typed differ's output (wrapped with change types eg. Add, Delete, Change)
|
11
|
+
HeckelDiffWrapper.new(old_text_array, new_text_array, diff_result).convert_to_typed_ouput
|
12
|
+
end
|
13
|
+
|
14
|
+
# Two-way diff based on the algorithm by P. Heckel.
|
15
|
+
# @param [in] left Array of anything implementing hash and equals
|
16
|
+
# @param [in] right Array of anything implementing hash and equals
|
17
|
+
def self.diff(left, right)
|
18
|
+
differ = HeckelDiff.new(left,right)
|
19
|
+
differ.perform_diff
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(left, right)
|
23
|
+
@left = left
|
24
|
+
@right = right
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform_diff
|
28
|
+
unique_positions = identify_unique_postions
|
29
|
+
unique_positions.sort!{ |a, b| a[0] <=> b[0] } # sort by the line in which the line was found in a
|
30
|
+
left_change_pos, right_change_pos = find_next_change
|
31
|
+
init_changes = ChangeData.new(left_change_pos, right_change_pos, [])
|
32
|
+
final_changes = unique_positions.reduce(init_changes, &method(:get_differences))
|
33
|
+
final_changes.change_ranges
|
34
|
+
end
|
35
|
+
|
36
|
+
ChangeData = Struct.new(:left_change_pos, :right_change_pos, :change_ranges)
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def get_differences(change_data, unique_positions)
|
41
|
+
left_pos, right_pos = change_data.left_change_pos, change_data.right_change_pos
|
42
|
+
left_uniq_pos, right_uniq_pos = unique_positions
|
43
|
+
if left_uniq_pos < left_pos || right_uniq_pos < right_pos
|
44
|
+
change_data
|
45
|
+
else
|
46
|
+
left_lo, left_hi, right_lo, right_hi = find_prev_change(left_pos, right_pos, left_uniq_pos-1, right_uniq_pos-1)
|
47
|
+
next_left_pos, next_right_pos = find_next_change(left_uniq_pos+1, right_uniq_pos+1)
|
48
|
+
|
49
|
+
updated_ranges = append_change_range(change_data.change_ranges, left_lo, left_hi, right_lo, right_hi)
|
50
|
+
ChangeData.new(next_left_pos, next_right_pos, updated_ranges)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_next_change(left_start_pos=0, right_start_pos=0)
|
55
|
+
l_arr, r_arr = (@left[left_start_pos..-1] || []), (@right[right_start_pos..-1] || [])
|
56
|
+
offset = mismatch_offset l_arr, r_arr
|
57
|
+
[ left_start_pos + offset, right_start_pos + offset]
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def find_prev_change(left_lo, right_lo, left_hi, right_hi)
|
62
|
+
if left_lo > left_hi || right_lo > right_hi
|
63
|
+
[left_lo, left_hi, right_lo, right_hi]
|
64
|
+
else
|
65
|
+
l_arr, r_arr = (@left[left_lo .. left_hi].reverse || []), (@right[right_lo .. right_hi].reverse || [])
|
66
|
+
offset = mismatch_offset l_arr, r_arr
|
67
|
+
[left_lo, left_hi - offset, right_lo, right_hi - offset]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def mismatch_offset(l_arr, r_arr)
|
72
|
+
_ , index = l_arr.zip(r_arr).each_with_index.detect { |pair, _| pair[0] != pair[1] }
|
73
|
+
index || [l_arr.length, r_arr.length].min
|
74
|
+
end
|
75
|
+
|
76
|
+
def identify_unique_postions
|
77
|
+
left_uniques = find_unique(@left)
|
78
|
+
right_uniques = find_unique(@right)
|
79
|
+
shared_keys = left_uniques.keys & right_uniques.keys
|
80
|
+
uniq_ranges = shared_keys.map { |k| [left_uniques[k], right_uniques[k]] }
|
81
|
+
uniq_ranges.unshift([ @left.length, @right.length])
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_unique(array)
|
85
|
+
flagged_uniques = array.each_with_index.reduce({}) do |hash, item_index|
|
86
|
+
item, pos = item_index
|
87
|
+
hash[item] = {pos: pos, unique: hash[item].nil?}
|
88
|
+
hash
|
89
|
+
end
|
90
|
+
flagged_uniques.select { |_, v| v[:unique] }.map { |k, v| [k, v[:pos]] }.to_h
|
91
|
+
end
|
92
|
+
|
93
|
+
# given the calculated bounds of the 2 way diff, create the proper change type and add it to the queue.
|
94
|
+
def append_change_range(changes_ranges, left_lo, left_hi, right_lo, right_hi)
|
95
|
+
if left_lo <= left_hi && right_lo <= right_hi # for this change, the bounds are both 'normal'. the beginning of the change is before the end.
|
96
|
+
changes_ranges << [:change, left_lo + 1, left_hi + 1, right_lo + 1, right_hi + 1]
|
97
|
+
elsif left_lo <= left_hi
|
98
|
+
changes_ranges << [:delete, left_lo + 1, left_hi + 1, right_lo + 1, right_lo]
|
99
|
+
elsif right_lo <= right_hi
|
100
|
+
changes_ranges << [:add, left_lo + 1, left_lo, right_lo + 1, right_hi + 1]
|
101
|
+
end
|
102
|
+
changes_ranges
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class TextNode
|
107
|
+
attr_accessor :text, :row
|
108
|
+
|
109
|
+
def initialize(text:, row:)
|
110
|
+
@text = text
|
111
|
+
@row = row
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class TwoWayChunk
|
116
|
+
attr_reader :action, :left_lo, :left_hi, :right_lo, :right_hi
|
117
|
+
def initialize(raw_chunk)
|
118
|
+
@action = raw_chunk[0]
|
119
|
+
@left_lo = raw_chunk[1]
|
120
|
+
@left_hi = raw_chunk[2]
|
121
|
+
@right_lo = raw_chunk[3]
|
122
|
+
@right_hi = raw_chunk[4]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
class HeckelDiffWrapper
|
126
|
+
def initialize(old_text_array, new_text_array, heckel_diff)
|
127
|
+
@chunks = heckel_diff.map { |block| TwoWayChunk.new(block) }
|
128
|
+
@old_text_array = old_text_array
|
129
|
+
@new_text_array = new_text_array
|
130
|
+
@old_text = []
|
131
|
+
@new_text = []
|
132
|
+
end
|
133
|
+
IndexTracker = Struct.new(:old_index, :new_index)
|
134
|
+
|
135
|
+
def convert_to_typed_ouput()
|
136
|
+
final_indexes = @chunks.reduce(IndexTracker.new(0,0)) do |index_tracker, chunk|
|
137
|
+
old_iteration, new_iteration = set_text_node_indexes(chunk, index_tracker.old_index, index_tracker.new_index)
|
138
|
+
old_index, new_index = append_changes(chunk, index_tracker.old_index + old_iteration, index_tracker.new_index + new_iteration)
|
139
|
+
IndexTracker.new(old_index, new_index)
|
140
|
+
end
|
141
|
+
|
142
|
+
set_the_remaining_text_node_indexes(final_indexes.old_index, final_indexes.new_index)
|
143
|
+
{ old_text: @old_text, new_text: @new_text}
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
def set_text_node_indexes(chunk, old_index, new_index)
|
148
|
+
old_iteration = 0
|
149
|
+
while old_index + old_iteration < chunk.left_lo - 1 # chunk indexes are from 1
|
150
|
+
@old_text << TextNode.new(text: @old_text_array[old_index + old_iteration], row: new_index + old_iteration)
|
151
|
+
old_iteration += 1
|
152
|
+
end
|
153
|
+
|
154
|
+
new_iteration = 0
|
155
|
+
while new_index + new_iteration < chunk.right_lo - 1 # chunk indexes are from 1
|
156
|
+
@new_text << TextNode.new(text: @new_text_array[new_index + new_iteration], row: old_index + new_iteration)
|
157
|
+
new_iteration += 1
|
158
|
+
end
|
159
|
+
[old_iteration, new_iteration]
|
160
|
+
end
|
161
|
+
|
162
|
+
def append_changes(chunk, old_index, new_index)
|
163
|
+
while old_index <= chunk.left_hi - 1 # chunk indexes are from 1
|
164
|
+
@old_text << @old_text_array[old_index]
|
165
|
+
old_index += 1
|
166
|
+
end
|
167
|
+
|
168
|
+
while new_index <= chunk.right_hi - 1 # chunk indexes are from 1
|
169
|
+
@new_text << @new_text_array[new_index]
|
170
|
+
new_index += 1
|
171
|
+
end
|
172
|
+
[old_index, new_index]
|
173
|
+
end
|
174
|
+
|
175
|
+
def set_the_remaining_text_node_indexes(old_index, new_index)
|
176
|
+
iteration = 0
|
177
|
+
while old_index + iteration < @old_text_array.length
|
178
|
+
@old_text << TextNode.new(text: @old_text_array[old_index + iteration], row: new_index + iteration)
|
179
|
+
iteration += 1
|
180
|
+
end
|
181
|
+
|
182
|
+
iteration = 0
|
183
|
+
while new_index + iteration < @new_text_array.length
|
184
|
+
@new_text << TextNode.new(text: @new_text_array[new_index + iteration], row: old_index + iteration)
|
185
|
+
iteration += 1
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module Dyph
|
2
|
+
module TwoWayDiffers
|
3
|
+
# rubocop:disable Metrics/ModuleLength
|
4
|
+
module OutputConverter
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def convert_to_dyph_output(old_text, new_text)
|
8
|
+
actions = merge_and_partition(old_text, new_text)
|
9
|
+
selected_actions = extract_add_deletes_changes(actions)
|
10
|
+
correct_offsets(selected_actions)
|
11
|
+
end
|
12
|
+
|
13
|
+
def objectify(merge_results)
|
14
|
+
merge_results.map do |result|
|
15
|
+
action = result[:action]
|
16
|
+
line = result[:line]
|
17
|
+
old_index = result[:old_index]
|
18
|
+
new_index = result[:new_index]
|
19
|
+
|
20
|
+
case action
|
21
|
+
when :add
|
22
|
+
Dyph::Action::Add.new(value: line, old_index: old_index, new_index: new_index)
|
23
|
+
when :delete
|
24
|
+
Dyph::Action::Delete.new(value: line, old_index: old_index, new_index: new_index)
|
25
|
+
when :no_change
|
26
|
+
Dyph::Action::NoChange.new(value: line.text, old_index: old_index, new_index: new_index)
|
27
|
+
else
|
28
|
+
raise "unhandled action"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def merge_results(old_text, new_text)
|
34
|
+
merged_text = []
|
35
|
+
|
36
|
+
if (new_text.empty?)
|
37
|
+
no_new_text(old_text, merged_text)
|
38
|
+
else
|
39
|
+
prepend_old_text(old_text, merged_text)
|
40
|
+
gather_up_actions(old_text, new_text, merged_text)
|
41
|
+
end
|
42
|
+
merged_text
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def merge_and_partition(old_text, new_text)
|
47
|
+
merged_text = merge_results(old_text, new_text)
|
48
|
+
merge_output_lines = merged_text.map { |x| to_output_format x }
|
49
|
+
partition_into_actions(merge_output_lines)
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_add_deletes_changes(actions)
|
53
|
+
collapsed_actions = actions.map { |action| collapse_action(action) }
|
54
|
+
paired_results = pair_up_add_deletes collapsed_actions
|
55
|
+
paired_results.reject { |res| res[:action] == :no_change}
|
56
|
+
end
|
57
|
+
|
58
|
+
def correct_offsets(selected_actions)
|
59
|
+
fix_offsets = set_offset(selected_actions)
|
60
|
+
fix_offsets.map { |r| Dyph::Support::AssignAction.get_action(
|
61
|
+
lo_a: r[:old_lo]-1 ,
|
62
|
+
lo_b: r[:new_lo]-1,
|
63
|
+
hi_a: r[:old_hi]-1,
|
64
|
+
hi_b: r[:new_hi]-1
|
65
|
+
)}
|
66
|
+
end
|
67
|
+
|
68
|
+
def gather_up_actions(old_text, new_text, merged_text)
|
69
|
+
prev_no_change_old = - 1
|
70
|
+
new_text.map.with_index.each do |line, i|
|
71
|
+
if !line.is_a?(TextNode)
|
72
|
+
merged_text << {action: :add, line: line, old_index: prev_no_change_old + 1, new_index: i+1}
|
73
|
+
else
|
74
|
+
prev_no_change_old = line.row
|
75
|
+
change_or_delete(old_text, line, prev_no_change_old, merged_text, i)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def change_or_delete(old_text, line, prev_no_change_old, merged_text, index)
|
81
|
+
merged_text << {action: :no_change, line: line, old_index: line.row, new_index: index}
|
82
|
+
|
83
|
+
((prev_no_change_old+1) ... old_text.length).each do |n|
|
84
|
+
break if old_text[n].is_a?(TextNode)
|
85
|
+
merged_text << {action: :delete, line: old_text[n], old_index: n+1, new_index: index+1}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def prepend_old_text(old_text, merged_text)
|
90
|
+
if !old_text.first.is_a?(TextNode)
|
91
|
+
old_text.map.with_index.each do |line, i|
|
92
|
+
break if line.is_a?(TextNode)
|
93
|
+
merged_text << {action: :delete, line: line, old_index:i+1, new_index: i}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def no_new_text(old_text, merged_text)
|
99
|
+
old_text.map.with_index do |line, i|
|
100
|
+
merged_text << { action: :delete, line: line, old_index: i+1, new_index: i}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_offset(results)
|
105
|
+
results.map do |result_row|
|
106
|
+
if result_row[:action] == :add
|
107
|
+
result_row[:old_lo] += 1
|
108
|
+
result_row
|
109
|
+
elsif result_row[:action] == :delete
|
110
|
+
result_row[:new_lo] += 1
|
111
|
+
result_row
|
112
|
+
else
|
113
|
+
result_row
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_output_format(result_row)
|
119
|
+
{
|
120
|
+
action: result_row[:action],
|
121
|
+
old_lo: result_row[:old_index],
|
122
|
+
old_hi: result_row[:old_index],
|
123
|
+
new_lo: result_row[:new_index],
|
124
|
+
new_hi: result_row[:new_index]
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def collapse_action(actions)
|
129
|
+
actions.inject({}) do | hash, action |
|
130
|
+
hash[:action] ||= action[:action]
|
131
|
+
hash[:old_lo] ||= action[:old_lo]
|
132
|
+
hash[:old_hi] = action[:old_hi]
|
133
|
+
hash[:new_lo] ||= action[:new_lo]
|
134
|
+
hash[:new_hi] = action[:new_hi]
|
135
|
+
hash
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def is_a_pair?(actions, i)
|
140
|
+
action_one = actions[i-1][:action] if actions[i-1]
|
141
|
+
action_two = actions[i][:action] if actions[i]
|
142
|
+
Set.new([action_one, action_two]) == Set.new([:add, :delete])
|
143
|
+
end
|
144
|
+
|
145
|
+
def pair_up_add_deletes(actions)
|
146
|
+
results = []
|
147
|
+
found_change = false
|
148
|
+
(1 .. actions.length).each do |i|
|
149
|
+
if is_a_pair?(actions, i)
|
150
|
+
results << {
|
151
|
+
action: :change,
|
152
|
+
old_lo: actions[i-1][:old_lo],
|
153
|
+
old_hi: actions[i-1][:old_hi],
|
154
|
+
new_lo: actions[i][:new_lo],
|
155
|
+
new_hi: actions[i][:new_hi]
|
156
|
+
}
|
157
|
+
found_change = true
|
158
|
+
elsif found_change
|
159
|
+
found_change = false
|
160
|
+
else
|
161
|
+
results << actions[i-1]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
results
|
165
|
+
end
|
166
|
+
|
167
|
+
def partition_into_actions(array)
|
168
|
+
array.inject([]) do |acc, x|
|
169
|
+
if acc.length == 0 || acc.last.last[:action] != x[:action]
|
170
|
+
acc << [x]
|
171
|
+
else
|
172
|
+
acc.last << x
|
173
|
+
end
|
174
|
+
acc
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/dyph/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dyph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Mook
|
8
|
+
- Andrew Montalto
|
9
|
+
- Jacob Elder
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2020-09-28 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.1'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '2.1'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rake
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: pry
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: pry-rescue
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: pry-stack_explorer
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rspec
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 3.3.0
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 3.3.0
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: awesome_print
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: factory_girl
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: rspec_junit_formatter
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: faker
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: yard
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
type: :development
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
description: A library of useful diffing algorithms for Ruby
|
170
|
+
email:
|
171
|
+
- kevin@kevinmook.com
|
172
|
+
executables: []
|
173
|
+
extensions: []
|
174
|
+
extra_rdoc_files: []
|
175
|
+
files:
|
176
|
+
- LICENSE
|
177
|
+
- README.md
|
178
|
+
- lib/dyph.rb
|
179
|
+
- lib/dyph/action.rb
|
180
|
+
- lib/dyph/action/add.rb
|
181
|
+
- lib/dyph/action/delete.rb
|
182
|
+
- lib/dyph/action/no_change.rb
|
183
|
+
- lib/dyph/differ.rb
|
184
|
+
- lib/dyph/equatable.rb
|
185
|
+
- lib/dyph/merge_result.rb
|
186
|
+
- lib/dyph/outcome.rb
|
187
|
+
- lib/dyph/outcome/conflicted.rb
|
188
|
+
- lib/dyph/outcome/resolved.rb
|
189
|
+
- lib/dyph/support/assign_action.rb
|
190
|
+
- lib/dyph/support/collater.rb
|
191
|
+
- lib/dyph/support/diff3.rb
|
192
|
+
- lib/dyph/support/merger.rb
|
193
|
+
- lib/dyph/support/sanity_check.rb
|
194
|
+
- lib/dyph/two_way_differs/heckel_diff.rb
|
195
|
+
- lib/dyph/two_way_differs/output_converter.rb
|
196
|
+
- lib/dyph/version.rb
|
197
|
+
homepage: https://github.com/kevinmookorg/dyph
|
198
|
+
licenses:
|
199
|
+
- MIT
|
200
|
+
metadata: {}
|
201
|
+
post_install_message:
|
202
|
+
rdoc_options: []
|
203
|
+
require_paths:
|
204
|
+
- lib
|
205
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: 2.6.0
|
210
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
215
|
+
requirements: []
|
216
|
+
rubygems_version: 3.0.3
|
217
|
+
signing_key:
|
218
|
+
specification_version: 4
|
219
|
+
summary: A library of useful diffing algorithms for Ruby
|
220
|
+
test_files: []
|