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
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