specdiff 0.2.0 → 0.3.0.rc2
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 +4 -4
- data/.gitignore +14 -0
- data/CHANGELOG.md +29 -0
- data/README.md +22 -6
- data/lib/specdiff/colorize.rb +5 -0
- data/lib/specdiff/compare.rb +2 -2
- data/lib/specdiff/diff.rb +10 -5
- data/lib/specdiff/differ/hash.rb +179 -0
- data/lib/specdiff/differ/not_found.rb +1 -1
- data/lib/specdiff/differ/text.rb +21 -7
- data/lib/specdiff/differ.rb +1 -1
- data/lib/specdiff/hashprint.rb +174 -0
- data/lib/specdiff/inspect.rb +134 -0
- data/lib/specdiff/rspec.rb +26 -0
- data/lib/specdiff/version.rb +1 -1
- data/lib/specdiff/webmock.rb +43 -1
- data/lib/specdiff.rb +15 -1
- data/specdiff.gemspec +6 -7
- metadata +14 -21
- data/.rspec +0 -3
- data/.rubocop.yml +0 -203
- data/.tool-versions +0 -1
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -76
- data/Rakefile +0 -12
- data/examples/webmock/Gemfile +0 -6
- data/examples/webmock/Gemfile.lock +0 -50
- data/examples/webmock/json.rb +0 -37
- data/examples/webmock/text.rb +0 -39
- data/lib/specdiff/differ/hashdiff.rb +0 -86
- data/lib/specdiff/webmock/request_body_diff.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a33e470568352bc5fb3079f5aa3cf9933f5c02d7e28aae98847b9165a0d6a86
|
4
|
+
data.tar.gz: ad35a03f7da4fee967890515447eb0d2a2a4ec927d78d2458198177166aa43c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fa7b514db6ccb4a7216f317184e6de0dae0fb8ad762222c4d09ee589300ae4d71a38549710ceb8191e5276958fcdfc27e65dcb50f0353e64065492c58a28b8a
|
7
|
+
data.tar.gz: bf66592a0f9d787058dcdd3b27910863abdc70cdeb177ddfb84d6ddfa9973adee345fa8c90f821f6a8dbab94898db375414eeef8e9a306b532b9aa472f615280
|
data/.gitignore
ADDED
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.3.0.rc2] - 2024-04-05
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Rework how hashdiff's output gets printed.
|
15
|
+
- Rework switching heuristic between text diff/hashdiff in hash differ.
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
|
19
|
+
- The RSpec integration now inspects hashes and arrays recursively. (Like rspec does by default)
|
20
|
+
- RSpec integration no longer breaks description output of matchers when using multi matchers (like .all or .and)
|
21
|
+
- The hash differ now deals with recursive hashes and arrays
|
22
|
+
- RSpec integration no longer breaks description output of matchers that are part of a diff inside an array or hash. (like when doing `match([have_attributes(...)])`)
|
23
|
+
|
24
|
+
## [0.3.0-rc1] - 2024-04-02
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
- Add rspec integration. `require "specdiff/rspec"` The rspec integration will cause rspec's differ to be replaced entirely with specdiff. It will also cause rspec's inspect (object formatter) to be replaced with Specdiff's inspect.
|
29
|
+
- Add `Specdiff.diff_inspect` method, which is a wrapper for `#inspect` with some extra logic to make diffs look better.
|
30
|
+
- Add `Specdiff.hashprint` method, which prints hashes/arrays recursively in a way that is friendly to a text differ.
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
|
34
|
+
- Improve contrast for some elements of the text differ
|
35
|
+
- Improve hash diffing. Introduce heuristic that decides whether a text diff of the hashes or the hashdiff gem's output is better for the situation.
|
36
|
+
- No longer produces a text diff of booleans
|
37
|
+
- No longer produces a text diff if both strings are a single line. (This would be useless since the diff is line-based.)
|
38
|
+
|
10
39
|
## [0.2.0] - 2023-12-04
|
11
40
|
|
12
41
|
### Changed
|
data/README.md
CHANGED
@@ -11,7 +11,24 @@ dropping the content type requirement.
|
|
11
11
|
Specdiff automagically detects the types of provided data and prints a suitable
|
12
12
|
diff between them.
|
13
13
|
|
14
|
-
|
14
|
+
## Cool, what does it look like?
|
15
|
+
|
16
|
+
When specdiff is enabled, webmock will produce a generic text diff using
|
17
|
+
[`Diff::LCS`](https://github.com/halostatue/diff-lcs) (basically the same
|
18
|
+
diff as rspec uses):
|
19
|
+
|
20
|
+

|
21
|
+
|
22
|
+
It will also produce a json [hashdiff](https://github.com/liufengyun/hashdiff),
|
23
|
+
even if the request did not have the content type header:
|
24
|
+
|
25
|
+

|
26
|
+
|
27
|
+
(The output of the json diff is experimental, feedback would be great!)
|
28
|
+
|
29
|
+
You might also check out the `examples/` directory to play with it:
|
30
|
+
|
31
|
+
`$ cd examples/webmock && bundle install`
|
15
32
|
|
16
33
|
## Installation
|
17
34
|
|
@@ -116,17 +133,16 @@ High level description of the heuristic specdiff implements
|
|
116
133
|
|
117
134
|
- [ ] unit tests are passing (`$ bundle exec rake test`)
|
118
135
|
- [ ] linter is happy (`$ bundle exec rake lint`)
|
119
|
-
- [ ]
|
120
|
-
- [ ] `$ bundle exec
|
136
|
+
- [ ] `examples/` look good
|
137
|
+
- [ ] check the package size using `$ bundle exec inspect_build`, make sure you haven't added any large files by accident
|
121
138
|
- [ ] update the version number in `version.rb`
|
122
|
-
- [ ] make sure the `examples/` `Gemfile.lock` files are updated
|
139
|
+
- [ ] make sure the `examples/` `Gemfile.lock` files are updated (run bundle install)
|
140
|
+
- [ ] make sure `Gemfile.lock` is updated (run bundle install)
|
123
141
|
- [ ] move unreleased changes to the next version in the [changelog](./CHANGELOG.md)
|
124
142
|
- [ ] commit in the form "vX.X.X" and push
|
125
143
|
- [ ] make sure the pipeline is green
|
126
144
|
- [ ] `$ bundle exec rake release`
|
127
145
|
|
128
|
-
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
129
|
-
|
130
146
|
## Contributing
|
131
147
|
|
132
148
|
Bug reports and pull requests are welcome on GitHub at https://github.com/odinhb/specdiff.
|
data/lib/specdiff/colorize.rb
CHANGED
data/lib/specdiff/compare.rb
CHANGED
@@ -80,9 +80,9 @@ private
|
|
80
80
|
elsif a.type == :text && b.type == :text
|
81
81
|
Specdiff::Differ::Text
|
82
82
|
elsif a.type == :hash && b.type == :hash
|
83
|
-
Specdiff::Differ::
|
83
|
+
Specdiff::Differ::Hash
|
84
84
|
elsif a.type == :array && b.type == :array
|
85
|
-
Specdiff::Differ::
|
85
|
+
Specdiff::Differ::Hash
|
86
86
|
else
|
87
87
|
Specdiff::Differ::NotFound
|
88
88
|
end
|
data/lib/specdiff/diff.rb
CHANGED
@@ -19,10 +19,15 @@
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
if empty?
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
raw_diff = if empty?
|
23
|
+
"empty"
|
24
|
+
elsif differ == ::Specdiff::Differ::Text
|
25
|
+
bytes = raw&.bytesize || 0
|
26
|
+
"#{bytes} bytes of #raw diff"
|
27
|
+
else
|
28
|
+
"#{raw.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
"<Specdiff::Diff (#{a.type}/#{b.type}) (#{differ}) (#{raw_diff})>"
|
27
32
|
end
|
28
33
|
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "hashdiff"
|
2
|
+
|
3
|
+
class Specdiff::Differ::Hash
|
4
|
+
extend ::Specdiff::Colorize
|
5
|
+
|
6
|
+
# The percentage of changes that must (potentially) be a key rename in a hash
|
7
|
+
# for text diffing to kick in. Expressed as a fraction of 1.
|
8
|
+
KEY_CHANGE_PERCENTAGE_THRESHOLD = 0.8
|
9
|
+
|
10
|
+
# The number of changes that must be detected by hashdiff before we print some
|
11
|
+
# extra newlines to better group extra/missing/new_values visually.
|
12
|
+
TOTAL_CHANGES_FOR_GROUPING_THRESHOLD = 9
|
13
|
+
|
14
|
+
def self.diff(a, b)
|
15
|
+
# array_path: true returns the path as an array, which differentiates
|
16
|
+
# between symbol keys and string keys in hashes, while the string
|
17
|
+
# representation does not.
|
18
|
+
# hmm it really seems like use_lcs: true gives much less human-readable
|
19
|
+
# (human-comprehensible) output when arrays are involved.
|
20
|
+
hashdiff_diff = ::Hashdiff.diff(
|
21
|
+
a.value, b.value,
|
22
|
+
array_path: true,
|
23
|
+
use_lcs: false,
|
24
|
+
)
|
25
|
+
|
26
|
+
return hashdiff_diff if hashdiff_diff.empty?
|
27
|
+
|
28
|
+
change_percentage = _calculate_change_percentage(hashdiff_diff)
|
29
|
+
|
30
|
+
if change_percentage <= KEY_CHANGE_PERCENTAGE_THRESHOLD
|
31
|
+
hashdiff_diff
|
32
|
+
else
|
33
|
+
a_text = ::Specdiff.hashprint(a.value)
|
34
|
+
b_text = ::Specdiff.hashprint(b.value)
|
35
|
+
|
36
|
+
text_diff = ::Specdiff.diff(a_text, b_text)
|
37
|
+
|
38
|
+
if text_diff.empty?
|
39
|
+
[]
|
40
|
+
else
|
41
|
+
text_diff
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self._calculate_change_percentage(hashdiff_diff)
|
47
|
+
extra_keys = hashdiff_diff.count { |element| element[0] == "+" }
|
48
|
+
missing_keys = hashdiff_diff.count { |element| element[0] == "-" }
|
49
|
+
new_values = hashdiff_diff.count { |element| element[0] == "~" }
|
50
|
+
# puts "hashdiff_diff: #{hashdiff_diff.inspect}"
|
51
|
+
# puts "extra_keys: #{extra_keys.inspect}"
|
52
|
+
# puts "missing_keys: #{missing_keys.inspect}"
|
53
|
+
# puts "new_values: #{new_values.inspect}"
|
54
|
+
|
55
|
+
potential_changed_keys = [extra_keys, missing_keys].min
|
56
|
+
adjusted_extra_keys = extra_keys - potential_changed_keys
|
57
|
+
adjusted_missing_keys = missing_keys - potential_changed_keys
|
58
|
+
# puts "potential_changed_keys: #{potential_changed_keys.inspect}"
|
59
|
+
# puts "adjusted_extra_keys: #{adjusted_extra_keys.inspect}"
|
60
|
+
# puts "adjusted_missing_keys: #{adjusted_missing_keys.inspect}"
|
61
|
+
|
62
|
+
non_changed_keys = adjusted_extra_keys + adjusted_missing_keys + new_values
|
63
|
+
total_changes = non_changed_keys + potential_changed_keys
|
64
|
+
# puts "non_changed_keys: #{non_changed_keys.inspect}"
|
65
|
+
# puts "total_changes: #{total_changes.inspect}"
|
66
|
+
|
67
|
+
key_change_fraction = Rational(potential_changed_keys, total_changes)
|
68
|
+
key_change_percentage = key_change_fraction.to_f
|
69
|
+
# puts "key_change_fraction: #{key_change_fraction.inspect}"
|
70
|
+
# puts "key_change_percentage: #{key_change_percentage.inspect}"
|
71
|
+
|
72
|
+
key_change_percentage
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.empty?(diff)
|
76
|
+
diff.raw.empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
NEWLINE = "\n"
|
80
|
+
|
81
|
+
def self.stringify(diff)
|
82
|
+
result = +""
|
83
|
+
return result if diff.empty?
|
84
|
+
|
85
|
+
total_changes = diff.raw.size
|
86
|
+
group_with_newlines = total_changes >= TOTAL_CHANGES_FOR_GROUPING_THRESHOLD
|
87
|
+
|
88
|
+
# hashdiff returns a structure like so:
|
89
|
+
# change[0] = '+', '-' or '~'. denoting type (addition, deletion or change)
|
90
|
+
# change[1] = the path to the change, in array form
|
91
|
+
# change[2] = the value, or the from value in case of '~'
|
92
|
+
# change[3] = the to value, only present when '~'
|
93
|
+
changes_grouped_by_type = diff.raw.group_by { |change| change[0] }
|
94
|
+
if (changes_grouped_by_type.keys - ["+", "-", "~"]).size > 0
|
95
|
+
$stderr.puts(
|
96
|
+
"Specdiff: hashdiff returned unexpected types: #{diff.raw.inspect}"
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
deletions = changes_grouped_by_type["-"] || []
|
101
|
+
additions = changes_grouped_by_type["+"] || []
|
102
|
+
value_changes = changes_grouped_by_type["~"] || []
|
103
|
+
|
104
|
+
result << "@@ +#{additions.size}/-#{deletions.size}/~#{value_changes.size} @@"
|
105
|
+
result << NEWLINE
|
106
|
+
|
107
|
+
deletions.each do |change|
|
108
|
+
value = change[2]
|
109
|
+
path = _stringify_path(change[1])
|
110
|
+
|
111
|
+
result << "missing key: #{path} (#{::Specdiff.diff_inspect(value)})"
|
112
|
+
result << NEWLINE
|
113
|
+
end
|
114
|
+
|
115
|
+
if deletions.any? && additions.any? && group_with_newlines
|
116
|
+
result << NEWLINE
|
117
|
+
end
|
118
|
+
|
119
|
+
additions.each do |change|
|
120
|
+
value = change[2]
|
121
|
+
path = _stringify_path(change[1])
|
122
|
+
|
123
|
+
result << " extra key: #{path} (#{::Specdiff.diff_inspect(value)})"
|
124
|
+
result << NEWLINE
|
125
|
+
end
|
126
|
+
|
127
|
+
if additions.any? && value_changes.any? && group_with_newlines
|
128
|
+
result << NEWLINE
|
129
|
+
end
|
130
|
+
|
131
|
+
value_changes.each do |change|
|
132
|
+
from = change[2]
|
133
|
+
to = change[3]
|
134
|
+
path = _stringify_path(change[1])
|
135
|
+
|
136
|
+
from_inspected = ::Specdiff.diff_inspect(from)
|
137
|
+
to_inspected = ::Specdiff.diff_inspect(to)
|
138
|
+
result << " new value: #{path} (#{from_inspected} -> #{to_inspected})"
|
139
|
+
result << NEWLINE
|
140
|
+
end
|
141
|
+
|
142
|
+
colorize_by_line(result) do |line|
|
143
|
+
if line.start_with?("missing key:")
|
144
|
+
red(line)
|
145
|
+
elsif line.start_with?(" extra key:")
|
146
|
+
green(line)
|
147
|
+
elsif line.start_with?(" new value:")
|
148
|
+
yellow(line)
|
149
|
+
elsif line.start_with?("@@")
|
150
|
+
cyan(line)
|
151
|
+
else
|
152
|
+
reset_color(line)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
PATH_SEPARATOR = ".".freeze
|
158
|
+
|
159
|
+
def self._stringify_path(path)
|
160
|
+
result = +""
|
161
|
+
|
162
|
+
path.each do |component|
|
163
|
+
if component.is_a?(Numeric)
|
164
|
+
result.chomp!(PATH_SEPARATOR)
|
165
|
+
result << "[#{component}]"
|
166
|
+
elsif component.is_a?(Symbol)
|
167
|
+
# by not inspecting symbols they look prettier than strings, but you
|
168
|
+
# can still tell the difference in the printed output
|
169
|
+
result << component.to_s
|
170
|
+
else
|
171
|
+
result << component.inspect
|
172
|
+
end
|
173
|
+
|
174
|
+
result << PATH_SEPARATOR
|
175
|
+
end
|
176
|
+
|
177
|
+
result.chomp(PATH_SEPARATOR)
|
178
|
+
end
|
179
|
+
end
|
data/lib/specdiff/differ/text.rb
CHANGED
@@ -15,20 +15,32 @@ class Specdiff::Differ::Text
|
|
15
15
|
b_value = b.value
|
16
16
|
|
17
17
|
if a_value.encoding != b_value.encoding
|
18
|
-
return <<~MSG
|
18
|
+
return colorize_by_line(<<~MSG) do |line|
|
19
19
|
Strings have different encodings:
|
20
20
|
#{a.value.encoding.inspect} != #{b.value.encoding.inspect}
|
21
21
|
MSG
|
22
|
+
# makes it stand out a bit more from the red of rspec output
|
23
|
+
reset_color(line)
|
24
|
+
end
|
22
25
|
end
|
23
26
|
|
24
27
|
diff = ""
|
25
28
|
|
29
|
+
# if there are no newlines then the text differ doesn't produce any valuable
|
30
|
+
# output. "word diffing" would improve this case.
|
31
|
+
if a_value.count(NEWLINE) <= 1 && b_value.count(NEWLINE) <= 1
|
32
|
+
return diff
|
33
|
+
end
|
34
|
+
|
26
35
|
a_lines = a_value.split(NEWLINE).map! { _1.chomp }
|
27
36
|
b_lines = b_value.split(NEWLINE).map! { _1.chomp }
|
37
|
+
|
38
|
+
file_length_difference = 0
|
39
|
+
|
28
40
|
hunks = ::Diff::LCS.diff(a_lines, b_lines).map do |piece|
|
29
41
|
::Diff::LCS::Hunk.new(
|
30
|
-
a_lines, b_lines, piece, CONTEXT_LINES,
|
31
|
-
)
|
42
|
+
a_lines, b_lines, piece, CONTEXT_LINES, file_length_difference,
|
43
|
+
).tap { |hunk| file_length_difference = hunk.file_length_difference }
|
32
44
|
end
|
33
45
|
|
34
46
|
hunks.each_cons(2) do |prev_hunk, current_hunk|
|
@@ -36,7 +48,7 @@ class Specdiff::Differ::Text
|
|
36
48
|
if current_hunk.overlaps?(prev_hunk)
|
37
49
|
current_hunk.merge(prev_hunk)
|
38
50
|
else
|
39
|
-
diff << prev_hunk.diff(:unified)
|
51
|
+
diff << prev_hunk.diff(:unified)
|
40
52
|
end
|
41
53
|
ensure
|
42
54
|
diff << NEWLINE
|
@@ -44,12 +56,14 @@ class Specdiff::Differ::Text
|
|
44
56
|
end
|
45
57
|
|
46
58
|
if hunks.last
|
47
|
-
diff <<
|
59
|
+
diff << NEWLINE
|
60
|
+
diff << hunks.last.diff(:unified)
|
48
61
|
end
|
49
62
|
|
50
63
|
return diff if diff == ""
|
51
64
|
|
52
|
-
diff <<
|
65
|
+
diff << NEWLINE
|
66
|
+
diff.lstrip!
|
53
67
|
|
54
68
|
return colorize_by_line(diff) do |line|
|
55
69
|
case line[0].chr
|
@@ -59,7 +73,7 @@ class Specdiff::Differ::Text
|
|
59
73
|
red(line)
|
60
74
|
when "@"
|
61
75
|
if line[1].chr == "@"
|
62
|
-
|
76
|
+
cyan(line)
|
63
77
|
else
|
64
78
|
reset_color(line)
|
65
79
|
end
|
data/lib/specdiff/differ.rb
CHANGED
@@ -0,0 +1,174 @@
|
|
1
|
+
# Tries to print nested hash/array structures such that they are as convenient
|
2
|
+
# as possible for a diffing algorithm designed for text (like source code).
|
3
|
+
# Basically tries to reproduce consistent literal source code form for ruby
|
4
|
+
# hashes and arrays and objects (although it falls back to #inspect).
|
5
|
+
class Specdiff::Hashprint
|
6
|
+
def self.call(...)
|
7
|
+
new.call(...)
|
8
|
+
end
|
9
|
+
|
10
|
+
INDENTATION_SPACES = 2
|
11
|
+
SPACE = " ".freeze
|
12
|
+
COMMA = ",".freeze
|
13
|
+
NEWLINE = "\n".freeze
|
14
|
+
|
15
|
+
def call(thing)
|
16
|
+
@recursion_trail = []
|
17
|
+
@indentation_level = 0
|
18
|
+
@indentation_per_level = SPACE * INDENTATION_SPACES
|
19
|
+
@indent = ""
|
20
|
+
@skip_next_opening_indent = false
|
21
|
+
|
22
|
+
@output = StringIO.new
|
23
|
+
|
24
|
+
output(thing)
|
25
|
+
|
26
|
+
@output.string
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def recalculate_indent
|
32
|
+
@indent = @indentation_per_level * @indentation_level
|
33
|
+
end
|
34
|
+
|
35
|
+
def increase_indentation
|
36
|
+
@indentation_level += 1
|
37
|
+
recalculate_indent
|
38
|
+
end
|
39
|
+
|
40
|
+
def decrease_indentation
|
41
|
+
@indentation_level -= 1
|
42
|
+
recalculate_indent
|
43
|
+
end
|
44
|
+
|
45
|
+
def skip_next_opening_indent
|
46
|
+
@skip_next_opening_indent = true
|
47
|
+
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def this_indent_should_be_skipped
|
52
|
+
if @skip_next_opening_indent
|
53
|
+
@skip_next_opening_indent = false
|
54
|
+
true
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def track_recursion(thing)
|
61
|
+
@recursion_trail.push(thing)
|
62
|
+
result = yield
|
63
|
+
@recursion_trail.pop
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def deja_vu?(current_place)
|
68
|
+
@recursion_trail.any? { |previous_place| previous_place == current_place }
|
69
|
+
end
|
70
|
+
|
71
|
+
# #=== allows us to rely on Module implementing #=== instead of relying on the
|
72
|
+
# thing (which could be any kind of wacky object) having to implement
|
73
|
+
# #is_a? or #kind_of?
|
74
|
+
def output(thing)
|
75
|
+
if deja_vu?(thing)
|
76
|
+
output_deja_vu(thing)
|
77
|
+
elsif Hash === thing
|
78
|
+
output_hash(thing)
|
79
|
+
elsif Array === thing
|
80
|
+
output_array(thing)
|
81
|
+
else
|
82
|
+
output_unknown(thing)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
HASH_OPEN = "{".freeze
|
87
|
+
HASH_CLOSE = "}".freeze
|
88
|
+
HASHROCKET = "=>".freeze
|
89
|
+
COLON = ":".freeze
|
90
|
+
|
91
|
+
def output_hash(hash)
|
92
|
+
@output << @indent unless this_indent_should_be_skipped
|
93
|
+
|
94
|
+
@output << HASH_OPEN
|
95
|
+
@output << NEWLINE
|
96
|
+
|
97
|
+
increase_indentation
|
98
|
+
track_recursion(hash) do
|
99
|
+
hash.each do |key, value|
|
100
|
+
@output << @indent
|
101
|
+
|
102
|
+
if key.is_a?(Symbol)
|
103
|
+
@output << key
|
104
|
+
@output << COLON
|
105
|
+
@output << SPACE
|
106
|
+
else
|
107
|
+
@output << ::Specdiff.diff_inspect(key)
|
108
|
+
@output << SPACE
|
109
|
+
@output << HASHROCKET
|
110
|
+
@output << SPACE
|
111
|
+
end
|
112
|
+
|
113
|
+
skip_next_opening_indent
|
114
|
+
output(value)
|
115
|
+
|
116
|
+
@output << COMMA
|
117
|
+
@output << NEWLINE
|
118
|
+
end
|
119
|
+
end
|
120
|
+
decrease_indentation
|
121
|
+
|
122
|
+
@output << @indent
|
123
|
+
@output << HASH_CLOSE
|
124
|
+
end
|
125
|
+
|
126
|
+
ARRAY_OPEN = "[".freeze
|
127
|
+
ARRAY_CLOSE = "]".freeze
|
128
|
+
|
129
|
+
def output_array(array)
|
130
|
+
@output << @indent unless this_indent_should_be_skipped
|
131
|
+
|
132
|
+
@output << ARRAY_OPEN
|
133
|
+
@output << NEWLINE
|
134
|
+
|
135
|
+
increase_indentation
|
136
|
+
track_recursion(array) do
|
137
|
+
array.each do |element|
|
138
|
+
output(element)
|
139
|
+
@output << COMMA
|
140
|
+
@output << NEWLINE
|
141
|
+
end
|
142
|
+
end
|
143
|
+
decrease_indentation
|
144
|
+
|
145
|
+
@output << @indent
|
146
|
+
@output << ARRAY_CLOSE
|
147
|
+
end
|
148
|
+
|
149
|
+
def output_unknown(thing)
|
150
|
+
@output << @indent unless this_indent_should_be_skipped
|
151
|
+
|
152
|
+
@output << ::Specdiff.diff_inspect(thing)
|
153
|
+
end
|
154
|
+
|
155
|
+
# The stdlib inspect code returns this when you have recursive structures.
|
156
|
+
STANDARD_INSPECT_RECURSIVE_ARRAY = "[...]".freeze
|
157
|
+
STANDARD_INSPECT_RECURSIVE_HASH = "{...}".freeze
|
158
|
+
|
159
|
+
def output_deja_vu(thing)
|
160
|
+
@output << @indent unless this_indent_should_be_skipped
|
161
|
+
|
162
|
+
case thing
|
163
|
+
when Array
|
164
|
+
# "#<Array ##{thing.object_id}>"
|
165
|
+
@output << STANDARD_INSPECT_RECURSIVE_ARRAY
|
166
|
+
when Hash
|
167
|
+
# "#<Hash ##{thing.object_id}>"
|
168
|
+
@output << STANDARD_INSPECT_RECURSIVE_HASH
|
169
|
+
else
|
170
|
+
# this should never happen
|
171
|
+
raise "Specdiff::Hashprint missing deja vu for: #{thing.inspect}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|