super_diff 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a67c51aa528c0539aca579800b31aafc363fb85f532ba9a07723e073a2efb050
4
+ data.tar.gz: 593449f1083f6a121b51bc74b30c78682f48b9f55e28335613b0babdbde0d4b0
5
+ SHA512:
6
+ metadata.gz: 867861728306a60b4ce417ac81836b933f6e5a6f36c27aa8dbe1735c3fd269a240c3901c2fa8cbec4158a65196053e0243da82d2c83567c8c5db12d6cb1b8627
7
+ data.tar.gz: 9148ff70d4edc39116fcd00100e5ab7cd3579c13f17eb6920ab6ba047df1e0dc02389b4600bfadd6ea7f73f686dc4881ff05d994c9574f4a97d04c13556eef09
data/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # SuperDiff [![Gem Version][version-badge]][rubygems] [![Build Status][travis-badge]][travis] ![Downloads][downloads-badge] [![Hound][hound-badge]][hound]
2
+
3
+ [version-badge]: http://img.shields.io/gem/v/super_diff.svg
4
+ [rubygems]: http://rubygems.org/gems/super_diff
5
+ [travis-badge]: http://img.shields.io/travis/mcmire/super_diff/master.svg
6
+ [travis]: http://travis-ci.org/mcmire/super_diff
7
+ [downloads-badge]: http://img.shields.io/gem/dtv/super_diff.svg
8
+ [hound-badge]: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
9
+ [hound]: https://houndci.com
10
+
11
+ ## Concept
12
+
13
+ SuperDiff is a utility that helps you diff two complex data structures in Ruby
14
+ and gives you helpful output to show you exactly how the two data structures
15
+ differ.
16
+
17
+ Let's say you have two hashes and you want to compare them. Perhaps your first
18
+ hash looks like this:
19
+
20
+ ``` ruby
21
+ expected = {
22
+ customer: {
23
+ name: "Marty McFly",
24
+ shipping_address: {
25
+ line_1: "123 Main St.",
26
+ city: "Hill Valley",
27
+ state: "CA",
28
+ zip: "90382",
29
+ },
30
+ },
31
+ items: [
32
+ {
33
+ name: "Fender Stratocaster",
34
+ cost: 100_000,
35
+ options: ["red", "blue", "green"],
36
+ },
37
+ { name: "Chevy 4x4" },
38
+ ],
39
+ }
40
+ ```
41
+
42
+ and your second hash looks like this:
43
+
44
+ ``` ruby
45
+ actual = {
46
+ customer: {
47
+ name: "Marty McFly, Jr.",
48
+ shipping_address: {
49
+ line_1: "456 Ponderosa Ct.",
50
+ city: "Hill Valley",
51
+ state: "CA",
52
+ zip: "90382",
53
+ },
54
+ },
55
+ items: [
56
+ {
57
+ name: "Fender Stratocaster",
58
+ cost: 100_000,
59
+ options: ["red", "blue", "green"],
60
+ },
61
+ { name: "Mattel Hoverboard" },
62
+ ],
63
+ }
64
+ ```
65
+
66
+ If you want to know what the difference between them is, you could say:
67
+
68
+ ``` ruby
69
+ SuperDiff::EqualityMatcher.call(expected, actual)
70
+ ```
71
+
72
+ This will give you the following string:
73
+
74
+ ```
75
+ Differing hashes.
76
+
77
+ Expected: { customer: { name: "Marty McFly", shipping_address: { line_1: "123 Main St.", city: "Hill Valley", state: "CA", zip: "90382" } }, items: [{ name: "Fender Stratocaster", cost: 100000, options: ["red", "blue", "green"] }, { name: "Chevy 4x4" }] }
78
+ Got: { customer: { name: "Marty McFly, Jr.", shipping_address: { line_1: "456 Ponderosa Ct.", city: "Hill Valley", state: "CA", zip: "90382" } }, items: [{ name: "Fender Stratocaster", cost: 100000, options: ["red", "blue", "green"] }, { name: "Mattel Hoverboard" }] }
79
+
80
+ Diff:
81
+
82
+ {
83
+ customer: {
84
+ - name: "Marty McFly",
85
+ + name: "Marty McFly, Jr.",
86
+ shipping_address: {
87
+ - line_1: "123 Main St.",
88
+ + line_1: "456 Ponderosa Ct.",
89
+ city: "Hill Valley",
90
+ state: "CA",
91
+ zip: "90382"
92
+ }
93
+ },
94
+ items: [
95
+ {
96
+ name: "Fender Stratocaster",
97
+ cost: 100000,
98
+ options: ["red", "blue", "green"]
99
+ },
100
+ {
101
+ - name: "Chevy 4x4"
102
+ + name: "Mattel Hoverboard"
103
+ }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ When printed to a terminal, this will display in color, so the "expected" value
109
+ in the summary and deleted lines in the diff will appear in red, while the
110
+ "actual" value in the summary and inserted lines in the diff will appear in
111
+ green.
112
+
113
+ By the way, SuperDiff doesn't just work with hashes, but arrays as well as other
114
+ objects, too!
115
+
116
+ ## Usage
117
+
118
+ There are two ways to use this gem. One way is to use the API methods that this
119
+ gem provides, such as the method presented above.
120
+
121
+ However, this gem was really designed for use specifically with RSpec. In recent
122
+ years, RSpec has added a feature where if you're comparing two objects in a test
123
+ and your test fails, you will see a diff between those objects (provided the
124
+ objects are large enough). However, this diff is not always the most helpful.
125
+ It's very common when writing tests for API endpoints to work with giant JSON
126
+ hashes, and RSpec's diffs are not sufficient in highlighting changes between
127
+ such structures. Therefore, this gem provides an integration layer where you can
128
+ replace RSpec's differ with SuperDiff.
129
+
130
+ To get started, add the gem to your Gemfile under the `test` group:
131
+
132
+ ``` ruby
133
+ gem "super_diff"
134
+ ```
135
+
136
+ Then, open up `spec_helper` and add this line somewhere:
137
+
138
+ ``` ruby
139
+ require "super_diff/rspec"
140
+ ```
141
+
142
+ Now try writing a test using `eq` to compare two large data structures, and you
143
+ should see a diff similar to the one given above.
144
+
145
+ ## Contributing
146
+
147
+ If you encounter a bug or have an idea for how this could be better, I'm all
148
+ ears! Feel free to create an issue.
149
+
150
+ If you'd like to submit a PR instead, here's how to get started. First, fork
151
+ this repo and then run:
152
+
153
+ ```
154
+ bundle install
155
+ ```
156
+
157
+ This will install dependencies. From here you can run all of the tests:
158
+
159
+ ```
160
+ bundle exec rake
161
+ ```
162
+
163
+ Or a single test:
164
+
165
+ ```
166
+ bundle exec rspec spec/acceptance/...
167
+ bundle exec rspec spec/unit/...
168
+ ```
169
+
170
+ Finally, submit your PR and I'll take a look at it when I get a chance.
171
+
172
+ ## Copyright/License
173
+
174
+ © 2018 Elliot Winkler, released under the [MIT license](LICENSE).
@@ -0,0 +1,52 @@
1
+ module SuperDiff
2
+ module Csi
3
+ module ColorHelper
4
+ BLACK = Csi::FourBitColor.new(:white)
5
+ # LIGHT_RED = Csi::TwentyFourBitColor.new(r: 63, g: 32, b: 32)
6
+ LIGHT_RED = Csi::TwentyFourBitColor.new(r: 73, g: 62, b: 71)
7
+ # RED = Csi::TwentyFourBitColor.new(r: 110, g: 56, b: 56)
8
+ # RED = Csi::TwentyFourBitColor.new(r: 116, g: 78, b: 84)
9
+ RED = Csi::FourBitColor.new(:red)
10
+ # LIGHT_GREEN = Csi::TwentyFourBitColor.new(r: 40, g: 48, b: 39)
11
+ LIGHT_GREEN = Csi::TwentyFourBitColor.new(r: 51, g: 81, b: 81)
12
+ # GREEN = Csi::TwentyFourBitColor.new(r: 73, g: 87, b: 71)
13
+ # GREEN = Csi::TwentyFourBitColor.new(r: 81, g: 115, b: 105)
14
+ GREEN = Csi::FourBitColor.new(:green)
15
+ DARK_GREY = Csi::TwentyFourBitColor.new(r: 134, g: 147, b: 149)
16
+
17
+ def self.plain(text)
18
+ text
19
+ end
20
+
21
+ def self.line(given_text = "")
22
+ if block_given?
23
+ text = ""
24
+ yield text
25
+ text + "\n"
26
+ else
27
+ given_text + "\n"
28
+ end
29
+ end
30
+
31
+ def self.light_red_bg(text)
32
+ Csi.colorize(text, fg: BLACK, bg: LIGHT_RED)
33
+ end
34
+
35
+ def self.red(text)
36
+ Csi.colorize(text, fg: RED)
37
+ end
38
+
39
+ def self.light_green_bg(text)
40
+ Csi.colorize(text, fg: BLACK, bg: LIGHT_GREEN)
41
+ end
42
+
43
+ def self.green(text)
44
+ Csi.colorize(text, fg: GREEN)
45
+ end
46
+
47
+ def self.dark_grey(text)
48
+ Csi.colorize(text, fg: BLACK, bg: DARK_GREY)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,131 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class EightBitColor
4
+ VALID_COMPONENT_RANGE = 0..6
5
+ VALID_CODE_RANGE = 0..255
6
+ VALID_CODES_BY_NAME = {
7
+ black: 0,
8
+ red: 1,
9
+ green: 2,
10
+ yellow: 3,
11
+ blue: 4,
12
+ magenta: 5,
13
+ cyan: 6,
14
+ white: 7,
15
+ bright_black: 8,
16
+ bright_red: 9,
17
+ bright_green: 10,
18
+ bright_yellow: 11,
19
+ bright_blue: 12,
20
+ bright_magenta: 13,
21
+ bright_cyan: 14,
22
+ bright_white: 15,
23
+ }.freeze
24
+ STARTING_INDICES = {
25
+ standard: 0,
26
+ high_intensity: 8,
27
+ grayscale: 232,
28
+ }.freeze
29
+ VALID_PAIR_TYPES = STARTING_INDICES.keys
30
+ VALID_PAIR_INDEX_RANGES = {
31
+ standard: 0..7,
32
+ high_intensity: 0..7,
33
+ grayscale: 0..23,
34
+ }.freeze
35
+ LEADING_CODES_BY_LAYER = { fg: 38, bg: 48 }.freeze
36
+ SERIAL_CODE = 5
37
+
38
+ def initialize(value)
39
+ @code =
40
+ case value
41
+ when Hash
42
+ interpret_triplet!(value)
43
+ when Symbol
44
+ interpret_color_name!(value)
45
+ when Array
46
+ interpret_pair!(value)
47
+ else
48
+ interpret_code!(value)
49
+ end
50
+ end
51
+
52
+ def sequence_for(layer)
53
+ leading_code = LEADING_CODES_BY_LAYER.fetch(layer)
54
+ "\e[#{leading_code};#{SERIAL_CODE};#{code}m"
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :code
60
+
61
+ def interpret_triplet!(spec)
62
+ unless spec.include?(:r) && spec.include?(:g) && spec.include?(:b)
63
+ raise ArgumentError.new(
64
+ "#{spec.inspect} is not a valid color specification. " +
65
+ "Please provide a hash with :r, :g, and :b keys.",
66
+ )
67
+ end
68
+
69
+ if spec.values.none? { |component| VALID_COMPONENT_RANGE.cover?(component) }
70
+ raise ArgumentError.new(
71
+ "(#{spec[:r]},#{spec[:g]},#{spec[:b]}) is not a valid color " +
72
+ "specification. All components must be between " +
73
+ "#{VALID_COMPONENT_RANGE.begin} and #{VALID_COMPONENT_RANGE.end}.",
74
+ )
75
+ end
76
+
77
+ 16 + 36 * spec[:r] + 6 * spec[:g] + spec[:b]
78
+ end
79
+
80
+ def interpret_color_name!(name)
81
+ if !VALID_CODES_BY_NAME.include?(name)
82
+ message =
83
+ "#{name.inspect} is not a valid color name.\n" +
84
+ "Valid names are:\n"
85
+
86
+ VALID_CODES_BY_NAME.keys.each do |valid_name|
87
+ message << "- #{valid_name}\n"
88
+ end
89
+
90
+ raise ArgumentError.new(message)
91
+ end
92
+
93
+ VALID_CODES_BY_NAME[name]
94
+ end
95
+
96
+ def interpret_pair!(pair)
97
+ type, index = pair
98
+
99
+ if !VALID_PAIR_TYPES.include?(type)
100
+ raise ArgumentError.new(
101
+ "Given pair did not have a valid type. " +
102
+ "Type must be one of: #{VALID_PAIR_TYPES}",
103
+ )
104
+ end
105
+
106
+ valid_range = VALID_PAIR_INDEX_RANGES[type]
107
+
108
+ if !valid_range.cover?(index)
109
+ raise ArgumentError.new(
110
+ "Given pair did not have a valid index. " +
111
+ "For #{type}, index must be between #{valid_range.begin} and " +
112
+ "#{valid_range.end}.",
113
+ )
114
+ end
115
+
116
+ STARTING_INDICES[type] + index
117
+ end
118
+
119
+ def interpret_code!(code)
120
+ if !VALID_CODE_RANGE.cover?(code)
121
+ raise ArgumentError.new(
122
+ "#{code.inspect} is not a valid color code " +
123
+ "(must be between 0 and 255).",
124
+ )
125
+ end
126
+
127
+ code
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,27 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class EightBitSequence
4
+ LEADING_CODES = { fg: 38, bg: 48 }.freeze
5
+ SERIAL_CODE = 5
6
+
7
+ def initialize(fg: nil, bg: nil)
8
+ @fg = fg
9
+ @bg = bg
10
+ end
11
+
12
+ def to_s
13
+ [sequence_for(:fg, fg), sequence_for(:bg, bg)].compact.join
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :fg, :bg
19
+
20
+ def sequence_for(type, color)
21
+ if color
22
+ "\e[#{LEADING_CODES.fetch(type)};#{SERIAL_CODE};#{color.code}m"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,80 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class FourBitColor
4
+ VALID_TYPES = [:fg, :bg].freeze
5
+ VALID_CODES_BY_NAME = {
6
+ black: { fg: 30, bg: 40 },
7
+ red: { fg: 31, bg: 41 },
8
+ green: { fg: 32, bg: 42 },
9
+ yellow: { fg: 33, bg: 43 },
10
+ blue: { fg: 34, bg: 44 },
11
+ magenta: { fg: 35, bg: 45 },
12
+ cyan: { fg: 36, bg: 46 },
13
+ white: { fg: 37, bg: 47 },
14
+ bright_black: { fg: 90, bg: 100 },
15
+ bright_red: { fg: 91, bg: 101 },
16
+ bright_green: { fg: 92, bg: 102 },
17
+ bright_yellow: { fg: 93, bg: 103 },
18
+ bright_blue: { fg: 94, bg: 104 },
19
+ bright_magenta: { fg: 95, bg: 105 },
20
+ bright_cyan: { fg: 96, bg: 106 },
21
+ bright_white: { fg: 97, bg: 107 },
22
+ }.freeze
23
+ NAMES_BY_CODE = VALID_CODES_BY_NAME.reduce({}) do |hash, (key, value)|
24
+ hash.merge(value[:fg] => key, value[:bg] => key)
25
+ end
26
+ VALID_NAMES = VALID_CODES_BY_NAME.keys
27
+ VALID_CODE_RANGES = [30..37, 40..47, 90..97, 100..107].freeze
28
+
29
+ def initialize(value)
30
+ @name =
31
+ if value.is_a?(Symbol)
32
+ interpret_name!(value)
33
+ else
34
+ interpret_code!(value)
35
+ end
36
+ end
37
+
38
+ def sequence_for(layer)
39
+ code = VALID_CODES_BY_NAME.fetch(name).fetch(layer)
40
+ "\e[#{code}m"
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :name
46
+
47
+ def interpret_name!(name)
48
+ if !VALID_NAMES.include?(name)
49
+ message =
50
+ "#{name.inspect} is not a valid color name.\n" +
51
+ "Valid names are:\n"
52
+
53
+ VALID_NAMES.each do |valid_name|
54
+ message << "- #{valid_name}"
55
+ end
56
+
57
+ raise ArgumentError.new(message)
58
+ end
59
+
60
+ name
61
+ end
62
+
63
+ def interpret_code!(code)
64
+ if VALID_CODE_RANGES.none? { |range| range.cover?(code) }
65
+ message =
66
+ "#{code.inspect} is not a valid color code.\n" +
67
+ "Valid codes are:\n"
68
+
69
+ VALID_CODE_RANGES.each do |range|
70
+ message << "- #{range.begin} through #{range.end}\n"
71
+ end
72
+
73
+ raise ArgumentError.new(message)
74
+ end
75
+
76
+ NAMES_BY_CODE.fetch(code)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,24 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class FourBitSequence
4
+ def initialize(fg: nil, bg: nil)
5
+ @fg = fg
6
+ @bg = bg
7
+ end
8
+
9
+ def to_s
10
+ [sequence_for(fg), sequence_for(bg)].compact.join
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :fg, :bg
16
+
17
+ def sequence_for(color)
18
+ if color
19
+ "\e[#{color.code}m"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class ResetSequence
4
+ def to_s
5
+ "\e[0m"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ module SuperDiff
2
+ module Csi
3
+ module Sequence
4
+ def self.for(color)
5
+ case color
6
+ when :reset
7
+ Csi::ResetSequence.new
8
+ when FourBitColor
9
+ Csi::FourBitSequence.new(color)
10
+ when EightBitColor
11
+ Csi::EightBitSequence.new(color)
12
+ when TwentyFourBitColor
13
+ Csi::TwentyFourBitSequence.new(color)
14
+ else
15
+ raise ArgumentError.new(
16
+ "Don't know how to interpret color: #{color.inspect}",
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class TwentyFourBitColor
4
+ VALID_COMPONENT_RANGE = 0..255
5
+ LEADING_CODES_BY_LAYER = { fg: 38, bg: 48 }.freeze
6
+ SERIAL_CODE = 2
7
+
8
+ def initialize(value)
9
+ @code = interpret_triplet!(value)
10
+ end
11
+
12
+ def sequence_for(layer)
13
+ leading_code = LEADING_CODES_BY_LAYER.fetch(layer)
14
+ "\e[#{leading_code};#{SERIAL_CODE};#{code}m"
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :code
20
+
21
+ def interpret_triplet!(spec)
22
+ if !spec.include?(:r) || !spec.include?(:g) || !spec.include?(:b)
23
+ raise ArgumentError.new(
24
+ "#{spec.inspect} is not a valid color specification. " +
25
+ "Please provide a hash with :r, :g, and :b keys.",
26
+ )
27
+ end
28
+
29
+ if spec.values.any? { |component| !VALID_COMPONENT_RANGE.cover?(component) }
30
+ raise ArgumentError.new(
31
+ "(#{spec[:r]},#{spec[:g]},#{spec[:b]}) is not a valid color " +
32
+ "specification. All components must be between " +
33
+ "#{VALID_COMPONENT_RANGE.begin} and #{VALID_COMPONENT_RANGE.end}.",
34
+ )
35
+ end
36
+
37
+ "#{spec[:r]};#{spec[:g]};#{spec[:b]}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module SuperDiff
2
+ module Csi
3
+ class TwentyFourBitSequence
4
+ LEADING_CODES = { fg: 38, bg: 48 }.freeze
5
+ SERIAL_CODE = 2
6
+
7
+ def initialize(fg: nil, bg: nil)
8
+ @fg = fg
9
+ @bg = bg
10
+ end
11
+
12
+ def to_s
13
+ [sequence_for(:fg, fg), sequence_for(:bg, bg)].compact.join
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :fg, :bg
19
+
20
+ def sequence_for(type, color)
21
+ if color
22
+ "\e[#{LEADING_CODES.fetch(type)};#{SERIAL_CODE};#{color.code}m"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "csi/reset_sequence"
2
+
3
+ # Source: <https://en.wikipedia.org/wiki/ANSI_escape_code>
4
+ module SuperDiff
5
+ module Csi
6
+ def self.reset_sequence
7
+ ResetSequence.new
8
+ end
9
+
10
+ def self.colorize(text, fg: nil, bg: nil)
11
+ parts = []
12
+
13
+ if fg
14
+ parts << fg.sequence_for(:fg)
15
+ end
16
+
17
+ if bg
18
+ parts << bg.sequence_for(:bg)
19
+ end
20
+
21
+ (parts + [text, reset_sequence]).join
22
+ end
23
+ end
24
+ end
25
+
26
+ require_relative "csi/four_bit_color"
27
+ require_relative "csi/eight_bit_color"
28
+ require_relative "csi/twenty_four_bit_color"
29
+ require_relative "csi/color_helper"
@@ -0,0 +1,37 @@
1
+ module SuperDiff
2
+ class DiffFormatter
3
+ def self.call(*args)
4
+ new(*args).call
5
+ end
6
+
7
+ def initialize(
8
+ operations,
9
+ indent_level:,
10
+ add_comma: false,
11
+ extra_classes: []
12
+ )
13
+ @operations = operations
14
+ @indent_level = indent_level
15
+ @add_comma = add_comma
16
+ @extra_classes = extra_classes
17
+ end
18
+
19
+ def call
20
+ resolved_class.call(
21
+ operations,
22
+ indent_level: indent_level,
23
+ add_comma: add_comma,
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :operations, :indent_level, :add_comma, :extra_classes
30
+
31
+ def resolved_class
32
+ (DiffFormatters::DEFAULTS + extra_classes).find do |klass|
33
+ klass.applies_to?(operations)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ module SuperDiff
2
+ module DiffFormatters
3
+ class Array < Base
4
+ def self.applies_to?(operations)
5
+ operations.is_a?(OperationSequences::Array)
6
+ end
7
+
8
+ def call
9
+ Collection.call(
10
+ open_token: "[",
11
+ close_token: "]",
12
+ collection_prefix: collection_prefix,
13
+ build_item_prefix: proc { "" },
14
+ operations: operations,
15
+ indent_level: indent_level,
16
+ add_comma: add_comma?,
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end