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.
- checksums.yaml +7 -0
- data/README.md +174 -0
- data/lib/super_diff/csi/color_helper.rb +52 -0
- data/lib/super_diff/csi/eight_bit_color.rb +131 -0
- data/lib/super_diff/csi/eight_bit_sequence.rb +27 -0
- data/lib/super_diff/csi/four_bit_color.rb +80 -0
- data/lib/super_diff/csi/four_bit_sequence.rb +24 -0
- data/lib/super_diff/csi/reset_sequence.rb +9 -0
- data/lib/super_diff/csi/sequence.rb +22 -0
- data/lib/super_diff/csi/twenty_four_bit_color.rb +41 -0
- data/lib/super_diff/csi/twenty_four_bit_sequence.rb +27 -0
- data/lib/super_diff/csi.rb +29 -0
- data/lib/super_diff/diff_formatter.rb +37 -0
- data/lib/super_diff/diff_formatters/array.rb +21 -0
- data/lib/super_diff/diff_formatters/base.rb +37 -0
- data/lib/super_diff/diff_formatters/collection.rb +107 -0
- data/lib/super_diff/diff_formatters/hash.rb +34 -0
- data/lib/super_diff/diff_formatters/multi_line_string.rb +31 -0
- data/lib/super_diff/diff_formatters/object.rb +27 -0
- data/lib/super_diff/diff_formatters.rb +5 -0
- data/lib/super_diff/differ.rb +48 -0
- data/lib/super_diff/differs/array.rb +24 -0
- data/lib/super_diff/differs/base.rb +42 -0
- data/lib/super_diff/differs/empty.rb +13 -0
- data/lib/super_diff/differs/hash.rb +24 -0
- data/lib/super_diff/differs/multi_line_string.rb +27 -0
- data/lib/super_diff/differs/object.rb +68 -0
- data/lib/super_diff/differs.rb +5 -0
- data/lib/super_diff/equality_matcher.rb +45 -0
- data/lib/super_diff/equality_matchers/array.rb +44 -0
- data/lib/super_diff/equality_matchers/base.rb +42 -0
- data/lib/super_diff/equality_matchers/hash.rb +44 -0
- data/lib/super_diff/equality_matchers/multi_line_string.rb +44 -0
- data/lib/super_diff/equality_matchers/object.rb +18 -0
- data/lib/super_diff/equality_matchers/single_line_string.rb +28 -0
- data/lib/super_diff/equality_matchers.rb +5 -0
- data/lib/super_diff/errors.rb +20 -0
- data/lib/super_diff/helpers.rb +96 -0
- data/lib/super_diff/operation_sequences/array.rb +14 -0
- data/lib/super_diff/operation_sequences/base.rb +11 -0
- data/lib/super_diff/operation_sequences/hash.rb +14 -0
- data/lib/super_diff/operation_sequences/object.rb +14 -0
- data/lib/super_diff/operational_sequencer.rb +43 -0
- data/lib/super_diff/operational_sequencers/array.rb +127 -0
- data/lib/super_diff/operational_sequencers/base.rb +97 -0
- data/lib/super_diff/operational_sequencers/hash.rb +82 -0
- data/lib/super_diff/operational_sequencers/multi_line_string.rb +85 -0
- data/lib/super_diff/operational_sequencers/object.rb +96 -0
- data/lib/super_diff/operational_sequencers.rb +5 -0
- data/lib/super_diff/operations/binary_operation.rb +47 -0
- data/lib/super_diff/operations/unary_operation.rb +25 -0
- data/lib/super_diff/rspec/differ.rb +30 -0
- data/lib/super_diff/rspec/monkey_patches.rb +122 -0
- data/lib/super_diff/rspec.rb +19 -0
- data/lib/super_diff/value_inspection.rb +11 -0
- data/lib/super_diff/version.rb +3 -0
- data/lib/super_diff.rb +50 -0
- data/spec/examples.txt +46 -0
- data/spec/integration/rspec_spec.rb +261 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/color_helper.rb +49 -0
- data/spec/support/command_runner.rb +279 -0
- data/spec/support/integration/matchers/produce_output_when_run_matcher.rb +76 -0
- data/spec/support/person.rb +23 -0
- data/spec/support/person_diff_formatter.rb +15 -0
- data/spec/support/person_operation_sequence.rb +14 -0
- data/spec/support/person_operational_sequencer.rb +19 -0
- data/spec/unit/equality_matcher_spec.rb +1233 -0
- data/super_diff.gemspec +23 -0
- 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,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
|