super_diff 0.1.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +174 -0
  3. data/lib/super_diff/csi/color_helper.rb +52 -0
  4. data/lib/super_diff/csi/eight_bit_color.rb +131 -0
  5. data/lib/super_diff/csi/eight_bit_sequence.rb +27 -0
  6. data/lib/super_diff/csi/four_bit_color.rb +80 -0
  7. data/lib/super_diff/csi/four_bit_sequence.rb +24 -0
  8. data/lib/super_diff/csi/reset_sequence.rb +9 -0
  9. data/lib/super_diff/csi/sequence.rb +22 -0
  10. data/lib/super_diff/csi/twenty_four_bit_color.rb +41 -0
  11. data/lib/super_diff/csi/twenty_four_bit_sequence.rb +27 -0
  12. data/lib/super_diff/csi.rb +29 -0
  13. data/lib/super_diff/diff_formatter.rb +37 -0
  14. data/lib/super_diff/diff_formatters/array.rb +21 -0
  15. data/lib/super_diff/diff_formatters/base.rb +37 -0
  16. data/lib/super_diff/diff_formatters/collection.rb +107 -0
  17. data/lib/super_diff/diff_formatters/hash.rb +34 -0
  18. data/lib/super_diff/diff_formatters/multi_line_string.rb +31 -0
  19. data/lib/super_diff/diff_formatters/object.rb +27 -0
  20. data/lib/super_diff/diff_formatters.rb +5 -0
  21. data/lib/super_diff/differ.rb +48 -0
  22. data/lib/super_diff/differs/array.rb +24 -0
  23. data/lib/super_diff/differs/base.rb +42 -0
  24. data/lib/super_diff/differs/empty.rb +13 -0
  25. data/lib/super_diff/differs/hash.rb +24 -0
  26. data/lib/super_diff/differs/multi_line_string.rb +27 -0
  27. data/lib/super_diff/differs/object.rb +68 -0
  28. data/lib/super_diff/differs.rb +5 -0
  29. data/lib/super_diff/equality_matcher.rb +45 -0
  30. data/lib/super_diff/equality_matchers/array.rb +44 -0
  31. data/lib/super_diff/equality_matchers/base.rb +42 -0
  32. data/lib/super_diff/equality_matchers/hash.rb +44 -0
  33. data/lib/super_diff/equality_matchers/multi_line_string.rb +44 -0
  34. data/lib/super_diff/equality_matchers/object.rb +18 -0
  35. data/lib/super_diff/equality_matchers/single_line_string.rb +28 -0
  36. data/lib/super_diff/equality_matchers.rb +5 -0
  37. data/lib/super_diff/errors.rb +20 -0
  38. data/lib/super_diff/helpers.rb +96 -0
  39. data/lib/super_diff/operation_sequences/array.rb +14 -0
  40. data/lib/super_diff/operation_sequences/base.rb +11 -0
  41. data/lib/super_diff/operation_sequences/hash.rb +14 -0
  42. data/lib/super_diff/operation_sequences/object.rb +14 -0
  43. data/lib/super_diff/operational_sequencer.rb +43 -0
  44. data/lib/super_diff/operational_sequencers/array.rb +127 -0
  45. data/lib/super_diff/operational_sequencers/base.rb +97 -0
  46. data/lib/super_diff/operational_sequencers/hash.rb +82 -0
  47. data/lib/super_diff/operational_sequencers/multi_line_string.rb +85 -0
  48. data/lib/super_diff/operational_sequencers/object.rb +96 -0
  49. data/lib/super_diff/operational_sequencers.rb +5 -0
  50. data/lib/super_diff/operations/binary_operation.rb +47 -0
  51. data/lib/super_diff/operations/unary_operation.rb +25 -0
  52. data/lib/super_diff/rspec/differ.rb +30 -0
  53. data/lib/super_diff/rspec/monkey_patches.rb +122 -0
  54. data/lib/super_diff/rspec.rb +19 -0
  55. data/lib/super_diff/value_inspection.rb +11 -0
  56. data/lib/super_diff/version.rb +3 -0
  57. data/lib/super_diff.rb +50 -0
  58. data/spec/examples.txt +46 -0
  59. data/spec/integration/rspec_spec.rb +261 -0
  60. data/spec/spec_helper.rb +44 -0
  61. data/spec/support/color_helper.rb +49 -0
  62. data/spec/support/command_runner.rb +279 -0
  63. data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +76 -0
  64. data/spec/support/person.rb +23 -0
  65. data/spec/support/person_diff_formatter.rb +15 -0
  66. data/spec/support/person_operation_sequence.rb +14 -0
  67. data/spec/support/person_operational_sequencer.rb +19 -0
  68. data/spec/unit/equality_matcher_spec.rb +1233 -0
  69. data/super_diff.gemspec +23 -0
  70. metadata +153 -0
@@ -0,0 +1,37 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class Base
4
+ def self.applies_to?(_operations)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def self.call(*args)
9
+ new(*args).call
10
+ end
11
+
12
+ def initialize(
13
+ operations,
14
+ indent_level:,
15
+ collection_prefix: "",
16
+ add_comma: false
17
+ )
18
+ @operations = operations
19
+ @indent_level = indent_level
20
+ @collection_prefix = collection_prefix
21
+ @add_comma = add_comma
22
+ end
23
+
24
+ def call
25
+ raise NotImplementedError
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :operations, :indent_level, :collection_prefix
31
+
32
+ def add_comma?
33
+ @add_comma
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,107 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class Collection
4
+ ICONS = { delete: "-", insert: "+" }.freeze
5
+ STYLES = { insert: :inserted, delete: :deleted, noop: :normal }.freeze
6
+
7
+ def self.call(*args, &block)
8
+ new(*args, &block).call
9
+ end
10
+
11
+ def initialize(
12
+ open_token:,
13
+ close_token:,
14
+ operations:,
15
+ indent_level:,
16
+ add_comma:,
17
+ collection_prefix:,
18
+ build_item_prefix:
19
+ )
20
+ @open_token = open_token
21
+ @close_token = close_token
22
+ @operations = operations
23
+ @indent_level = indent_level
24
+ @add_comma = add_comma
25
+ @collection_prefix = collection_prefix
26
+ @build_item_prefix = build_item_prefix
27
+ end
28
+
29
+ def call
30
+ lines.join("\n")
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :open_token, :close_token, :operations, :indent_level,
36
+ :add_comma, :collection_prefix, :build_item_prefix
37
+
38
+ def lines
39
+ [
40
+ " #{indentation}#{collection_prefix}#{open_token}",
41
+ *contents,
42
+ " #{indentation}#{close_token}#{comma}",
43
+ ]
44
+ end
45
+
46
+ def contents
47
+ operations.map do |operation|
48
+ if operation.name == :change
49
+ operation.child_operations.to_diff(
50
+ indent_level: indent_level + 1,
51
+ collection_prefix: build_item_prefix.call(operation),
52
+ add_comma: operation.should_add_comma_after_displaying?,
53
+ )
54
+ else
55
+ icon = ICONS.fetch(operation.name, " ")
56
+ style_name = STYLES.fetch(operation.name, :normal)
57
+ chunk = build_chunk(
58
+ Helpers.inspect_object(operation.value, single_line: false),
59
+ prefix: build_item_prefix.call(operation),
60
+ icon: icon,
61
+ )
62
+
63
+ if operation.should_add_comma_after_displaying?
64
+ chunk << ","
65
+ end
66
+
67
+ style_chunk(style_name, chunk)
68
+ end
69
+ end
70
+ end
71
+
72
+ def build_chunk(content, prefix:, icon:)
73
+ lines =
74
+ if content.is_a?(ValueInspection)
75
+ [
76
+ indentation(offset: 1) + prefix + content.beginning,
77
+ *content.middle.map { |line| indentation(offset: 2) + line },
78
+ indentation(offset: 1) + content.end,
79
+ ]
80
+ else
81
+ [indentation(offset: 1) + prefix + content]
82
+ end
83
+
84
+ lines.map { |line| icon + " " + line }.join("\n")
85
+ end
86
+
87
+ def style_chunk(style_name, chunk)
88
+ chunk.
89
+ split("\n").
90
+ map { |line| Helpers.style(style_name, line) }.
91
+ join("\n")
92
+ end
93
+
94
+ def indentation(offset: 0)
95
+ " " * ((indent_level + offset) * 2)
96
+ end
97
+
98
+ def comma
99
+ if add_comma
100
+ ","
101
+ else
102
+ ""
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,34 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class Hash < Base
4
+ def self.applies_to?(operations)
5
+ operations.is_a?(OperationSequences::Hash)
6
+ end
7
+
8
+ def call
9
+ Collection.call(
10
+ open_token: "{",
11
+ close_token: "}",
12
+ collection_prefix: collection_prefix,
13
+ build_item_prefix: -> (operation) {
14
+ key =
15
+ if operation.respond_to?(:left_key)
16
+ operation.left_key
17
+ else
18
+ operation.key
19
+ end
20
+
21
+ if key.is_a?(Symbol)
22
+ "#{key}: "
23
+ else
24
+ "#{key.inspect} => "
25
+ end
26
+ },
27
+ operations: operations,
28
+ indent_level: indent_level,
29
+ add_comma: add_comma?,
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class MultiLineString < Base
4
+ def self.applies_to?(operations)
5
+ operations.is_a?(OperationSequences::MultiLineString)
6
+ end
7
+
8
+ def call
9
+ lines.join("\n")
10
+ end
11
+
12
+ private
13
+
14
+ def lines
15
+ operations.reduce([]) do |array, operation|
16
+ case operation.name
17
+ when :change
18
+ array << Helpers.style(:deleted, "- #{operation.left_value}")
19
+ array << Helpers.style(:inserted, "+ #{operation.right_value}")
20
+ when :delete
21
+ array << Helpers.style(:deleted, "- #{operation.value}")
22
+ when :insert
23
+ array << Helpers.style(:inserted, "+ #{operation.value}")
24
+ else
25
+ array << Helpers.style(:normal, " #{operation.value}")
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class Object < Base
4
+ def self.applies_to?(operations)
5
+ operations.is_a?(OperationSequences::Object)
6
+ end
7
+
8
+ def call
9
+ Collection.call(
10
+ open_token: "#<#{value_class} {",
11
+ close_token: "}>",
12
+ collection_prefix: collection_prefix,
13
+ build_item_prefix: -> (operation) { "#{operation.key}: " },
14
+ operations: operations,
15
+ indent_level: indent_level,
16
+ add_comma: add_comma?,
17
+ )
18
+ end
19
+
20
+ protected
21
+
22
+ def value_class
23
+ raise NotImplementedError
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ DEFAULTS = [Array, Hash, MultiLineString].freeze
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ module SuperDiff
2
+ class Differ
3
+ def self.call(*args)
4
+ new(*args).call
5
+ end
6
+
7
+ def initialize(
8
+ expected,
9
+ actual,
10
+ indent_level: 0,
11
+ index_in_collection: nil,
12
+ extra_classes: [],
13
+ extra_operational_sequencer_classes: [],
14
+ extra_diff_formatter_classes: []
15
+ )
16
+ @expected = expected
17
+ @actual = actual
18
+ @indent_level = indent_level
19
+ @index_in_collection = index_in_collection
20
+ @extra_classes = extra_classes
21
+ @extra_operational_sequencer_classes = extra_operational_sequencer_classes
22
+ @extra_diff_formatter_classes = extra_diff_formatter_classes
23
+ end
24
+
25
+ def call
26
+ resolved_class.call(
27
+ expected,
28
+ actual,
29
+ indent_level: indent_level,
30
+ index_in_collection: index_in_collection,
31
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
32
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :expected, :actual, :indent_level, :index_in_collection,
39
+ :extra_classes, :extra_operational_sequencer_classes,
40
+ :extra_diff_formatter_classes
41
+
42
+ def resolved_class
43
+ (Differs::DEFAULTS + extra_classes).find do |klass|
44
+ klass.applies_to?(expected) && klass.applies_to?(actual)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class Array < Base
4
+ def self.applies_to?(value)
5
+ value.is_a?(::Array)
6
+ end
7
+
8
+ def call
9
+ DiffFormatters::Array.call(operations, indent_level: indent_level)
10
+ end
11
+
12
+ private
13
+
14
+ def operations
15
+ OperationalSequencers::Array.call(
16
+ expected: expected,
17
+ actual: actual,
18
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
19
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class Base
4
+ def self.applies_to?(_value)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def self.call(*args)
9
+ new(*args).call
10
+ end
11
+
12
+ def initialize(
13
+ expected,
14
+ actual,
15
+ indent_level:,
16
+ index_in_collection: nil,
17
+ extra_operational_sequencer_classes: [],
18
+ extra_diff_formatter_classes: []
19
+ )
20
+ @expected = expected
21
+ @actual = actual
22
+ @indent_level = indent_level
23
+ @index_in_collection = index_in_collection
24
+ @extra_operational_sequencer_classes = extra_operational_sequencer_classes
25
+ @extra_diff_formatter_classes = extra_diff_formatter_classes
26
+ end
27
+
28
+ def call
29
+ raise NotImplementedError
30
+ end
31
+
32
+ protected
33
+
34
+ attr_reader :expected, :actual, :indent_level, :index_in_collection,
35
+ :extra_operational_sequencer_classes, :extra_diff_formatter_classes
36
+
37
+ def indentation
38
+ " " * (indent_level * 2)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class Empty < Base
4
+ def self.applies_to?(_value)
5
+ true
6
+ end
7
+
8
+ def call
9
+ ""
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class Hash < Base
4
+ def self.applies_to?(value)
5
+ value.is_a?(::Hash)
6
+ end
7
+
8
+ def call
9
+ DiffFormatters::Hash.call(operations, indent_level: indent_level)
10
+ end
11
+
12
+ private
13
+
14
+ def operations
15
+ OperationalSequencers::Hash.call(
16
+ expected: expected,
17
+ actual: actual,
18
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
19
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class MultiLineString < Base
4
+ def self.applies_to?(value)
5
+ value.is_a?(::String) && value.include?("\n")
6
+ end
7
+
8
+ def call
9
+ DiffFormatters::MultiLineString.call(
10
+ operations,
11
+ indent_level: indent_level,
12
+ )
13
+ end
14
+
15
+ private
16
+
17
+ def operations
18
+ OperationalSequencers::MultiLineString.call(
19
+ expected: expected,
20
+ actual: actual,
21
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
22
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ module SuperDiff
2
+ module Differs
3
+ class Object < Base
4
+ def self.applies_to?(value)
5
+ value.is_a?(::Object)
6
+ end
7
+
8
+ def self.call(
9
+ expected,
10
+ actual,
11
+ indent_level:,
12
+ index_in_collection:,
13
+ **rest
14
+ )
15
+ super(
16
+ expected,
17
+ actual,
18
+ indent_level: indent_level,
19
+ index_in_collection: index_in_collection,
20
+ **rest
21
+ )
22
+ end
23
+
24
+ def self.inspection_lines_for(value)
25
+ inspected_value = value.inspect
26
+ match = inspected_value.match(/\A#<([^ ]+)(.*)>\Z/)
27
+
28
+ if match
29
+ [
30
+ "#<#{match.captures[0]} {",
31
+ *match.captures[1].split(" ").map { |line| " " + line },
32
+ "}>",
33
+ ]
34
+ else
35
+ [inspected_value]
36
+ end
37
+ end
38
+
39
+ def call
40
+ lines.join("\n")
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :expected, :actual, :indent_level, :index_in_collection
46
+
47
+ def lines
48
+ [
49
+ styled_lines_for("-", :deleted, expected),
50
+ styled_lines_for("+", :inserted, actual),
51
+ ]
52
+ end
53
+
54
+ def styled_lines_for(icon, style_name, value)
55
+ unstyled_lines_for(icon, value).map do |line|
56
+ Helpers.style(style_name, line)
57
+ end
58
+ end
59
+
60
+ def unstyled_lines_for(icon, value)
61
+ lines = self.class.inspection_lines_for(value).
62
+ map { |inspection_line| "#{icon} #{indentation}#{inspection_line}" }
63
+
64
+ lines
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ module SuperDiff
2
+ module Differs
3
+ DEFAULTS = [Array, Hash, MultiLineString, Object, Empty].freeze
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ module SuperDiff
2
+ class EqualityMatcher
3
+ def self.call(*args)
4
+ new(*args).call
5
+ end
6
+
7
+ def initialize(
8
+ expected:,
9
+ actual:,
10
+ extra_classes: [],
11
+ extra_operational_sequencer_classes: [],
12
+ extra_diff_formatter_classes: []
13
+ )
14
+ @expected = expected
15
+ @actual = actual
16
+ @extra_classes = extra_classes
17
+ @extra_operational_sequencer_classes = extra_operational_sequencer_classes
18
+ @extra_diff_formatter_classes = extra_diff_formatter_classes
19
+ end
20
+
21
+ def call
22
+ resolved_class.call(
23
+ expected: expected,
24
+ actual: actual,
25
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
26
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
27
+ )
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :expected, :actual, :extra_classes,
33
+ :extra_operational_sequencer_classes, :extra_diff_formatter_classes
34
+
35
+ def resolved_class
36
+ matching_class || EqualityMatchers::Object
37
+ end
38
+
39
+ def matching_class
40
+ (EqualityMatchers::DEFAULTS + extra_classes).find do |klass|
41
+ klass.applies_to?(expected) && klass.applies_to?(actual)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ module SuperDiff
2
+ module EqualityMatchers
3
+ class Array < Base
4
+ def self.applies_to?(value)
5
+ value.class == ::Array
6
+ end
7
+
8
+ def fail
9
+ <<~OUTPUT.strip
10
+ Differing arrays.
11
+
12
+ #{
13
+ Helpers.style(
14
+ :deleted,
15
+ "Expected: #{Helpers.inspect_object(expected)}",
16
+ )
17
+ }
18
+ #{
19
+ Helpers.style(
20
+ :inserted,
21
+ " Actual: #{Helpers.inspect_object(actual)}",
22
+ )
23
+ }
24
+
25
+ Diff:
26
+
27
+ #{diff}
28
+ OUTPUT
29
+ end
30
+
31
+ protected
32
+
33
+ def diff
34
+ Differs::Array.call(
35
+ expected,
36
+ actual,
37
+ indent_level: 0,
38
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
39
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ module SuperDiff
2
+ module EqualityMatchers
3
+ class Base
4
+ def self.applies_to?(_value)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def self.call(*args)
9
+ new(*args).call
10
+ end
11
+
12
+ def initialize(
13
+ expected:,
14
+ actual:,
15
+ extra_operational_sequencer_classes: [],
16
+ extra_diff_formatter_classes: []
17
+ )
18
+ @expected = expected
19
+ @actual = actual
20
+ @extra_operational_sequencer_classes = extra_operational_sequencer_classes
21
+ @extra_diff_formatter_classes = extra_diff_formatter_classes
22
+ end
23
+
24
+ def call
25
+ if expected == actual
26
+ ""
27
+ else
28
+ fail
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ attr_reader :expected, :actual, :extra_operational_sequencer_classes,
35
+ :extra_diff_formatter_classes
36
+
37
+ def fail
38
+ raise NotImplementedError
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ module SuperDiff
2
+ module EqualityMatchers
3
+ class Hash < Base
4
+ def self.applies_to?(value)
5
+ value.class == ::Hash
6
+ end
7
+
8
+ def fail
9
+ <<~OUTPUT.strip
10
+ Differing hashes.
11
+
12
+ #{
13
+ Helpers.style(
14
+ :deleted,
15
+ "Expected: #{Helpers.inspect_object(expected)}",
16
+ )
17
+ }
18
+ #{
19
+ Helpers.style(
20
+ :inserted,
21
+ " Actual: #{Helpers.inspect_object(actual)}",
22
+ )
23
+ }
24
+
25
+ Diff:
26
+
27
+ #{diff}
28
+ OUTPUT
29
+ end
30
+
31
+ protected
32
+
33
+ def diff
34
+ Differs::Hash.call(
35
+ expected,
36
+ actual,
37
+ indent_level: 0,
38
+ extra_operational_sequencer_classes: extra_operational_sequencer_classes,
39
+ extra_diff_formatter_classes: extra_diff_formatter_classes,
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end