specdiff 0.3.0.pre.rc1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +14 -0
- data/CHANGELOG.md +21 -1
- data/DEVELOPMENT.md +74 -0
- data/README.md +138 -98
- data/glossary.txt +4 -0
- data/lib/specdiff/compare.rb +2 -2
- data/lib/specdiff/diff.rb +10 -7
- data/lib/specdiff/differ/{hashdiff.rb → hash.rb} +53 -34
- data/lib/specdiff/differ.rb +1 -1
- data/lib/specdiff/hashprint.rb +82 -55
- data/lib/specdiff/inspect.rb +94 -1
- data/lib/specdiff/rspec.rb +8 -13
- data/lib/specdiff/version.rb +1 -1
- data/lib/specdiff/webmock.rb +5 -2
- data/lib/specdiff.rb +0 -5
- data/specdiff.gemspec +17 -10
- metadata +19 -29
- 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/assets/webmock_json_with_specdiff.png +0 -0
- data/assets/webmock_text_with_specdiff.png +0 -0
- data/examples/rspec/.rspec +0 -2
- data/examples/rspec/Gemfile +0 -10
- data/examples/rspec/Gemfile.lock +0 -52
- data/examples/rspec/spec/example_spec.rb +0 -678
- data/examples/rspec/spec/spec_helper.rb +0 -68
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16d43f0ff6248844478dc69d99ebb9e5de1212804fba96b859d947e446e5fae7
|
4
|
+
data.tar.gz: 0c9e973795920664617d2c57211e40569a0aee10ee6b6c35b02680a6789edf2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a21ca995b7418a69deaeb6c024f40b2fc6342200edc33f4179f979ce7555bf346b29012184784b59f0736ac55b089a55836dd21756e14100d7a76260b5c7d69a
|
7
|
+
data.tar.gz: e457b0266ddc5f11df693baae9594dd790498e687ce7a4d263b9b291108af2a14f976499dd3b1eee9294a6827e2f15f527c0ae451035c8f73dc2dbaea9245f2d
|
data/.gitignore
ADDED
data/CHANGELOG.md
CHANGED
@@ -7,7 +7,27 @@ 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-
|
10
|
+
## [0.3.0] - 2024-04-16
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Hashprint now sorts keys (if all the keys are strings or symbols), leading to better text diffs from the hash differ.
|
15
|
+
|
16
|
+
## [0.3.0.rc2] - 2024-04-05
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- Rework how hashdiff's output gets printed.
|
21
|
+
- Rework switching heuristic between text diff/hashdiff in hash differ.
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- The RSpec integration now inspects hashes and arrays recursively. (Like rspec does by default)
|
26
|
+
- RSpec integration no longer breaks description output of matchers when using multi matchers (like .all or .and)
|
27
|
+
- The hash differ now deals with recursive hashes and arrays
|
28
|
+
- 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(...)])`)
|
29
|
+
|
30
|
+
## [0.3.0-rc1] - 2024-04-02
|
11
31
|
|
12
32
|
### Added
|
13
33
|
|
data/DEVELOPMENT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Development
|
2
|
+
|
3
|
+
## Glossary
|
4
|
+
|
5
|
+
Check out the [glossary](./glossary.txt) to make sure we are using the same
|
6
|
+
words for things.
|
7
|
+
|
8
|
+
## Setup
|
9
|
+
|
10
|
+
Install the software versions specified in `.tool-versions`.
|
11
|
+
|
12
|
+
Run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run
|
13
|
+
the tests and linter and make sure they're green
|
14
|
+
before starting to make your changes.
|
15
|
+
|
16
|
+
Run `bundle exec rake -AD` for a full list of all the available tasks you may use for development purposes.
|
17
|
+
|
18
|
+
Run `bundle exec rake` to run the tests and linter.
|
19
|
+
|
20
|
+
## Examples
|
21
|
+
|
22
|
+
The `examples/` directory is used as an ad-hoc integration testing method.
|
23
|
+
[Read more here](./examples/)
|
24
|
+
|
25
|
+
## Pull request checklist
|
26
|
+
|
27
|
+
- [ ] Make sure the tests are passing
|
28
|
+
- [ ] Make sure the linter is happy
|
29
|
+
- [ ] Make sure the `examples/` look the same or have suffered and improvement
|
30
|
+
- [ ] Update the unreleased section of the [changelog](./CHANGELOG.md) with a human readable explanation of your changes
|
31
|
+
|
32
|
+
## Architecture
|
33
|
+
|
34
|
+
High level description of the heuristic specdiff implements
|
35
|
+
|
36
|
+
1. receive 2 pieces of data: `a` and `b`
|
37
|
+
2. determine types for `a` and `b`
|
38
|
+
1. test against plugin types
|
39
|
+
2. test against built in types
|
40
|
+
3. fall back to the `:unknown` type
|
41
|
+
3. determine which differ is appropriate for the types
|
42
|
+
1. test against plugin differs
|
43
|
+
2. test against built in differs
|
44
|
+
3. fall back to the null differ (`NotFound`)
|
45
|
+
7. run the selected differ with a and b
|
46
|
+
8. package it into a `::Specdiff::Diff` which records the detected types
|
47
|
+
|
48
|
+
\<time passes>
|
49
|
+
|
50
|
+
6. at some point later when `#to_s` is invoked, stringify the diff using the differ's `#stringify`
|
51
|
+
|
52
|
+
## Maintainer's notes
|
53
|
+
|
54
|
+
### Release procedure
|
55
|
+
|
56
|
+
- [ ] unit tests are passing (`$ bundle exec rake test`)
|
57
|
+
- [ ] linter is happy (`$ bundle exec rake lint`)
|
58
|
+
- [ ] `examples/` look good
|
59
|
+
- [ ] check the package size using `$ bundle exec inspect_build`, make sure you haven't added any large files by accident
|
60
|
+
- [ ] update the version number in `version.rb`
|
61
|
+
- [ ] make sure the `examples/` `Gemfile.lock` files are updated (run bundle install)
|
62
|
+
- [ ] make sure `Gemfile.lock` is updated (run bundle install)
|
63
|
+
- [ ] move unreleased changes to the next version in the [changelog](./CHANGELOG.md)
|
64
|
+
- [ ] commit in the form "vX.X.X" and push
|
65
|
+
- [ ] make sure the pipeline is green
|
66
|
+
- [ ] `$ bundle exec rake release`
|
67
|
+
|
68
|
+
### Contemplated improvements (AKA the todo list)
|
69
|
+
|
70
|
+
- [ ] word diff
|
71
|
+
- [ ] yard documentation?
|
72
|
+
- [ ] 2.7.0 support
|
73
|
+
- [ ] actual integration tests (replacing `examples/`)
|
74
|
+
- [ ] real documentation for the plugin interface
|
data/README.md
CHANGED
@@ -1,36 +1,40 @@
|
|
1
1
|
# Specdiff
|
2
2
|
|
3
|
-
|
3
|
+
Specdiff is an opinionated gem that provides diffing between arbitrary data. It
|
4
|
+
was built in an effort to improve [WebMock](https://rubygems.org/gems/webmock)'s
|
5
|
+
and [RSpec](https://rubygems.org/gems/rspec)'s diff output, and
|
6
|
+
comes with integrations for both. It produces output that looks very similiar to
|
7
|
+
RSpec, but with better support for nested hash/array structures.
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
if they happen to both be json (including setting content type). This gem aims
|
8
|
-
to bring text diffing (ala rspec) to webmock via monkey-patch, as well as
|
9
|
-
dropping the content type requirement.
|
9
|
+
By default, specdiff will produce diff output for multiline text by using
|
10
|
+
[diff-lcs](https://rubygems.org/gems/diff-lcs):
|
10
11
|
|
11
|
-
|
12
|
-
diff between them.
|
12
|
+
![example of multiline text](assets/specdiff_multiline_text_diff_example.png)
|
13
13
|
|
14
|
-
|
14
|
+
It also diffs hashes and arrays:
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
diff
|
16
|
+
| ![img](assets/specdiff_hashdiff_diff_example.png) | ![img](assets/specdiff_text_based_hash_diff_example.png) |
|
17
|
+
| --- | --- |
|
18
|
+
| Hashdiff based hash diff | Text based hash diff |
|
19
19
|
|
20
|
-
|
20
|
+
Specdiff will automatically switch between these two types of hash/array diff
|
21
|
+
output depending on what it thinks is more readable. The first type of output
|
22
|
+
showcased here comes from the
|
23
|
+
[hashdiff](https://rubygems.org/gems/hashdiff) gem, one of specdiff's
|
24
|
+
dependencies. The other is the *text diff* run on the output of *hashprint*.
|
25
|
+
Specdiff switches to using hashprint/text diff when it estimates that a lot of
|
26
|
+
the keys changed names, which is a point of weakness for hashdiff's output.
|
21
27
|
|
22
|
-
|
23
|
-
even if the request did not have the content type header:
|
24
|
-
|
25
|
-
![d](./assets/webmock_json_with_specdiff.png)
|
28
|
+
## Installation
|
26
29
|
|
27
|
-
|
30
|
+
### Requirements
|
28
31
|
|
29
|
-
|
32
|
+
Specdiff is tested to work with:
|
33
|
+
- Ruby `>= 3.0`.
|
34
|
+
- RSpec `3.13.0`, `3.12.2`, `3.12.0`.
|
35
|
+
- WebMock `3.19.1`, `3.18.1`.
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
## Installation
|
37
|
+
### Use bundler
|
34
38
|
|
35
39
|
Add this line to your application's Gemfile:
|
36
40
|
|
@@ -40,123 +44,159 @@ gem "specdiff", require: false
|
|
40
44
|
|
41
45
|
And then execute:
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
Or install it yourself as:
|
47
|
+
`$ bundle install`
|
46
48
|
|
47
|
-
|
49
|
+
### Loading the gem and its integrations
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
Put the following in your `spec_helper.rb`. (Or equivalent initializer
|
52
|
-
for test environment) You probably don't want to load/use this gem in a release
|
53
|
-
environment.
|
51
|
+
Put the following in the initializer for your test environment (`spec_helper.rb`):
|
54
52
|
|
55
53
|
```rb
|
56
|
-
#
|
57
|
-
|
58
|
-
require "specdiff"
|
59
|
-
|
54
|
+
require "specdiff" # may be unneccessary if you're using bundler to load your gems
|
55
|
+
require "specdiff/rspec" # optional, enables RSpec integration
|
56
|
+
require "specdiff/webmock" # optional, enables WebMock integration
|
57
|
+
Specdiff.load!(:json) # optional, automatically detects json and uses the hash differ on it
|
60
58
|
|
61
|
-
# optionally, you can turn off terminal colors
|
62
59
|
Specdiff.configure do |config|
|
63
|
-
config.colorize = true
|
60
|
+
config.colorize = true # toggles color output
|
64
61
|
end
|
65
62
|
```
|
66
63
|
|
64
|
+
The monkey-patches should go after the gems they are patching.
|
65
|
+
|
66
|
+
*This gem is intended for use in test environments, not production or release environments.*
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
Specdiff has two integrations: [WebMock](https://rubygems.org/gems/webmock) and
|
71
|
+
[RSpec](https://rubygems.org/gems/rspec). These are the main way specdiff is
|
72
|
+
intended to be used.
|
73
|
+
|
74
|
+
### RSpec
|
75
|
+
|
76
|
+
The RSpec integration improves on RSpec's built in diffing functionality by
|
77
|
+
diffing nested hash/array structures with
|
78
|
+
[hashdiff](https://rubygems.org/gems/hashdiff). This produces clearer
|
79
|
+
output when deeply nested structures are involved:
|
80
|
+
|
81
|
+
| ![img](assets/source_hashdiff_hash.png) | ![img](assets/rspecs_hashdiff_hash.png) | ![img](assets/specdiffs_hashdiff_hash.png) |
|
82
|
+
| --- | --- | --- |
|
83
|
+
| Test source | RSpec's diff | Specdiff's diff |
|
84
|
+
|
85
|
+
In some cases specdiff may produce a text-based diff of deeply nested hashes or
|
86
|
+
arrays instead. This also represents an improvement over RSpec's text-based diff
|
87
|
+
of nested hashes/arrays, by virtue of custom pretty-printing code designed for
|
88
|
+
the text differ to work on:
|
89
|
+
|
90
|
+
| ![img](assets/source_text_hash.png) | ![img](assets/rspecs_text_hash.png) | ![img](assets/specdiffs_text_hash.png) |
|
91
|
+
| --- | --- | --- |
|
92
|
+
| Test source | RSpec's diff | Specdiff's diff |
|
93
|
+
|
94
|
+
The RSpec integration also prevents RSpec from truncating your data before
|
95
|
+
printing it (by replacing the inspect implementation), avoiding the necessity
|
96
|
+
of a diff in some instances:
|
97
|
+
|
98
|
+
| ![img](assets/rspec_truncating.png) | ![img](assets/specdiff_no_truncating.png) |
|
99
|
+
| --- | --- |
|
100
|
+
| RSpec truncating your data | Specdiff preventing truncation |
|
101
|
+
|
102
|
+
(Although this is an instance where a "word diff" would be more helpful)
|
103
|
+
|
67
104
|
### WebMock
|
68
105
|
|
69
|
-
|
70
|
-
|
106
|
+
WebMock already ships with a dependency on
|
107
|
+
[hashdiff](https://rubygems.org/gems/hashdiff), providing diffs of json request
|
108
|
+
stubs. Specdiff prints hashdiff's output differently, and does not require the
|
109
|
+
content type to be specified:
|
71
110
|
|
72
|
-
|
111
|
+
(This requires you to enable the json plugin)
|
73
112
|
|
74
|
-
|
113
|
+
| ![img](assets/webmock_json_diff_source.png) | ![img](assets/webmock_json_diff.png) | ![img](assets/webmock_json_diff_specdiff.png) |
|
114
|
+
| --- | --- | --- |
|
115
|
+
| Source | WebMock | Specdiff |
|
75
116
|
|
76
|
-
|
77
|
-
diff = Specdiff.diff(something, and_something_else)
|
117
|
+
With specdiff enabled you also get text diffs where previously there were none:
|
78
118
|
|
79
|
-
|
80
|
-
|
81
|
-
|
119
|
+
| ![img](assets/webmock_newlines_diff_source.png) | ![img](assets/webmock_newlines_diff.png) | ![img](assets/webmock_newlines_diff_specdiff.png) |
|
120
|
+
| --- | --- | --- |
|
121
|
+
| Source | WebMock | Specdiff |
|
82
122
|
|
83
|
-
|
123
|
+
The WebMock integration continues to respect the `show_body_diff` setting in
|
124
|
+
WebMock:
|
84
125
|
|
85
126
|
```rb
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
Specdiff.load!(MyCustomType)
|
127
|
+
# this will cause diffs to not be produced, regardless of whether specdiff is
|
128
|
+
# loaded. body diffs are enabled by default in WebMock so you don't need to
|
129
|
+
# touch this.
|
130
|
+
WebMock.hide_body_diff!
|
91
131
|
```
|
92
132
|
|
93
|
-
|
133
|
+
### Direct usage
|
94
134
|
|
95
|
-
|
135
|
+
It is also possible to call into specdiff with arbitrary data.
|
136
|
+
This is essentially what the integrations do for you.
|
137
|
+
This is suitable if you are developing a library and intend to depend on
|
138
|
+
specdiff for diff output.
|
96
139
|
|
97
|
-
|
98
|
-
|
140
|
+
```rb
|
141
|
+
# Generate a diff, using all plugins available.
|
142
|
+
diff = Specdiff.diff(something, anything)
|
99
143
|
|
100
|
-
|
144
|
+
diff.empty? # => true/false, if true you probably don't want to actually show the diff
|
145
|
+
diff.to_s # => a String for showing to a developer who may or may not be scratching their head
|
101
146
|
|
102
|
-
|
147
|
+
# Generate an indented, pretty-printed string representation of a ruby hash.
|
148
|
+
printed_hash = Specdiff.hashprint(my_big_nested_hash)
|
149
|
+
printed_hash # => String
|
150
|
+
|
151
|
+
# Inspect something, but with prettier output for certain classes
|
152
|
+
# (Time/DateTime/BigDecimal). This is not indented. Usually defers to #inspect.
|
153
|
+
specdiff_inspected = Specdiff.diff_inspect(something)
|
154
|
+
specdiff_inspected # => String
|
155
|
+
```
|
103
156
|
|
104
|
-
|
157
|
+
Any methods or properties not documented here are considered private
|
158
|
+
implementation details.
|
105
159
|
|
106
|
-
|
160
|
+
## Plugins
|
107
161
|
|
108
|
-
|
162
|
+
It is possible to create and load plugins into specdiff to customize Specdiff's
|
163
|
+
behaviour. This was implemented in case you have special needs (e.g. diffing
|
164
|
+
things other than text, hashes, arrays and json).
|
109
165
|
|
110
|
-
|
166
|
+
### JSON
|
111
167
|
|
112
|
-
|
168
|
+
The built-in json plugin is optional, and loaded like so:
|
113
169
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
2. test against built in types
|
118
|
-
3. fall back to the `:unknown` type
|
119
|
-
3. determine which differ is appropriate for the types
|
120
|
-
1. test against plugin differs
|
121
|
-
2. test against built in differs
|
122
|
-
3. fall back to the null differ (`NotFound`)
|
123
|
-
7. run the selected differ with a and b
|
124
|
-
8. package it into a `::Specdiff::Diff` which records the detected types
|
170
|
+
```rb
|
171
|
+
Specdiff.load!(:json)
|
172
|
+
```
|
125
173
|
|
126
|
-
|
174
|
+
The JSON plugin attempts to parse strings into json using `JSON.parse`. If
|
175
|
+
successful, it considers that string a json, and will use the parse result as if
|
176
|
+
it had been passed directly to specdiff in the first place.
|
127
177
|
|
128
|
-
|
178
|
+
### Defining your own plugins
|
129
179
|
|
130
|
-
|
180
|
+
While other plugins are loaded by simply passing them to `::load!`:
|
131
181
|
|
132
|
-
|
182
|
+
```rb
|
183
|
+
class MySpecdiffPlugin
|
184
|
+
# Read the source code to figure out how plugins work. Sorry.
|
185
|
+
end
|
133
186
|
|
134
|
-
|
135
|
-
|
136
|
-
- [ ] `examples/` look good
|
137
|
-
- [ ] update the version number in `version.rb`
|
138
|
-
- [ ] make sure the `examples/` `Gemfile.lock` files are updated (run bundle install)
|
139
|
-
- [ ] make sure `Gemfile.lock` is updated (run bundle install)
|
140
|
-
- [ ] move unreleased changes to the next version in the [changelog](./CHANGELOG.md)
|
141
|
-
- [ ] commit in the form "vX.X.X" and push
|
142
|
-
- [ ] make sure the pipeline is green
|
143
|
-
- [ ] `$ bundle exec rake release`
|
187
|
+
Specdiff.load!(MySpecdiffPlugin)
|
188
|
+
```
|
144
189
|
|
145
|
-
|
190
|
+
This was intended to allow implementing (potentially janky and inefficient)
|
191
|
+
plugins to help diff XML, but in practice the text differ was good enough for
|
192
|
+
diffing indented XML.
|
146
193
|
|
147
194
|
## Contributing
|
148
195
|
|
196
|
+
Read [DEVELOPMENT.md](./DEVELOPMENT.md) for current development practices.
|
197
|
+
|
149
198
|
Bug reports and pull requests are welcome on GitHub at https://github.com/odinhb/specdiff.
|
150
199
|
|
151
200
|
## License
|
152
201
|
|
153
202
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
154
|
-
|
155
|
-
## TODO
|
156
|
-
|
157
|
-
This documents potential improvements/issues I know about or have thought about.
|
158
|
-
|
159
|
-
- [ ] test the webmock monkey-patch. currently there is an empty webmock_spec.rb (should we do this using rspec?) and the examples/ directory contains a few webmock examples (which are a good idea to run before releasing) but it would be nice to have the pipeline fail if it doesn't work for whatever reason
|
160
|
-
- [ ] finalize plugin interface (are the methods named intuitively? should we split type detector definitions and differ definitions?)
|
161
|
-
- [ ] document how to define a plugin properly (instead of just linking to the source code)
|
162
|
-
- [ ] is the stringification of hashdiff's output really better than pretty print? or just more wordy? (the colors are definitely nice)
|
data/glossary.txt
CHANGED
@@ -44,3 +44,7 @@ side
|
|
44
44
|
compare
|
45
45
|
the procedure that implements the main function of specdiff including
|
46
46
|
accounting for any plugin types and differs
|
47
|
+
|
48
|
+
integration
|
49
|
+
refers to the provided monkey-patches and conscious relationship between
|
50
|
+
specdiff and another gem or piece of software it was intended to work with.
|
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,12 +19,15 @@
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
if empty?
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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})>"
|
29
32
|
end
|
30
33
|
end
|
@@ -1,19 +1,27 @@
|
|
1
1
|
require "hashdiff"
|
2
2
|
|
3
|
-
class Specdiff::Differ::
|
3
|
+
class Specdiff::Differ::Hash
|
4
4
|
extend ::Specdiff::Colorize
|
5
5
|
|
6
|
-
|
7
|
-
|
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
|
8
9
|
|
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
|
10
13
|
|
11
14
|
def self.diff(a, b)
|
12
15
|
# array_path: true returns the path as an array, which differentiates
|
13
16
|
# between symbol keys and string keys in hashes, while the string
|
14
17
|
# representation does not.
|
18
|
+
|
15
19
|
# hmm it really seems like use_lcs: true gives much less human-readable
|
16
20
|
# (human-comprehensible) output when arrays are involved.
|
21
|
+
|
22
|
+
# use_lcs: true may also cause Hashdiff to use a lot of memory when BIG
|
23
|
+
# arrays are involved: https://github.com/liufengyun/hashdiff/issues/49
|
24
|
+
# so we might as well avoid that problem altogether.
|
17
25
|
hashdiff_diff = ::Hashdiff.diff(
|
18
26
|
a.value, b.value,
|
19
27
|
array_path: true,
|
@@ -24,54 +32,60 @@ class Specdiff::Differ::Hashdiff
|
|
24
32
|
|
25
33
|
change_percentage = _calculate_change_percentage(hashdiff_diff)
|
26
34
|
|
27
|
-
if change_percentage
|
35
|
+
if change_percentage <= KEY_CHANGE_PERCENTAGE_THRESHOLD
|
28
36
|
hashdiff_diff
|
29
37
|
else
|
30
38
|
a_text = ::Specdiff.hashprint(a.value)
|
31
39
|
b_text = ::Specdiff.hashprint(b.value)
|
32
40
|
|
33
|
-
|
41
|
+
text_diff = ::Specdiff.diff(a_text, b_text)
|
34
42
|
|
35
|
-
if
|
43
|
+
if text_diff.empty?
|
36
44
|
[]
|
37
45
|
else
|
38
|
-
|
39
|
-
diff.b.type = b.type
|
40
|
-
|
41
|
-
diff
|
46
|
+
text_diff
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
51
|
def self._calculate_change_percentage(hashdiff_diff)
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
extra_keys = hashdiff_diff.count { |element| element[0] == "+" }
|
53
|
+
missing_keys = hashdiff_diff.count { |element| element[0] == "-" }
|
54
|
+
new_values = hashdiff_diff.count { |element| element[0] == "~" }
|
50
55
|
# puts "hashdiff_diff: #{hashdiff_diff.inspect}"
|
51
|
-
# puts "
|
52
|
-
# puts "
|
53
|
-
# puts "
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# puts "
|
65
|
-
|
66
|
-
|
56
|
+
# puts "extra_keys: #{extra_keys.inspect}"
|
57
|
+
# puts "missing_keys: #{missing_keys.inspect}"
|
58
|
+
# puts "new_values: #{new_values.inspect}"
|
59
|
+
|
60
|
+
potential_changed_keys = [extra_keys, missing_keys].min
|
61
|
+
adjusted_extra_keys = extra_keys - potential_changed_keys
|
62
|
+
adjusted_missing_keys = missing_keys - potential_changed_keys
|
63
|
+
# puts "potential_changed_keys: #{potential_changed_keys.inspect}"
|
64
|
+
# puts "adjusted_extra_keys: #{adjusted_extra_keys.inspect}"
|
65
|
+
# puts "adjusted_missing_keys: #{adjusted_missing_keys.inspect}"
|
66
|
+
|
67
|
+
non_changed_keys = adjusted_extra_keys + adjusted_missing_keys + new_values
|
68
|
+
total_changes = non_changed_keys + potential_changed_keys
|
69
|
+
# puts "non_changed_keys: #{non_changed_keys.inspect}"
|
70
|
+
# puts "total_changes: #{total_changes.inspect}"
|
71
|
+
|
72
|
+
key_change_fraction = Rational(potential_changed_keys, total_changes)
|
73
|
+
key_change_percentage = key_change_fraction.to_f
|
74
|
+
# puts "key_change_fraction: #{key_change_fraction.inspect}"
|
75
|
+
# puts "key_change_percentage: #{key_change_percentage.inspect}"
|
76
|
+
|
77
|
+
key_change_percentage
|
67
78
|
end
|
68
79
|
|
69
80
|
def self.empty?(diff)
|
70
81
|
diff.raw.empty?
|
71
82
|
end
|
72
83
|
|
84
|
+
NEWLINE = "\n"
|
85
|
+
|
73
86
|
def self.stringify(diff)
|
74
87
|
result = +""
|
88
|
+
return result if diff.empty?
|
75
89
|
|
76
90
|
total_changes = diff.raw.size
|
77
91
|
group_with_newlines = total_changes >= TOTAL_CHANGES_FOR_GROUPING_THRESHOLD
|
@@ -92,6 +106,9 @@ class Specdiff::Differ::Hashdiff
|
|
92
106
|
additions = changes_grouped_by_type["+"] || []
|
93
107
|
value_changes = changes_grouped_by_type["~"] || []
|
94
108
|
|
109
|
+
result << "@@ +#{additions.size}/-#{deletions.size}/~#{value_changes.size} @@"
|
110
|
+
result << NEWLINE
|
111
|
+
|
95
112
|
deletions.each do |change|
|
96
113
|
value = change[2]
|
97
114
|
path = _stringify_path(change[1])
|
@@ -108,7 +125,7 @@ class Specdiff::Differ::Hashdiff
|
|
108
125
|
value = change[2]
|
109
126
|
path = _stringify_path(change[1])
|
110
127
|
|
111
|
-
result << "
|
128
|
+
result << " extra key: #{path} (#{::Specdiff.diff_inspect(value)})"
|
112
129
|
result << NEWLINE
|
113
130
|
end
|
114
131
|
|
@@ -123,17 +140,19 @@ class Specdiff::Differ::Hashdiff
|
|
123
140
|
|
124
141
|
from_inspected = ::Specdiff.diff_inspect(from)
|
125
142
|
to_inspected = ::Specdiff.diff_inspect(to)
|
126
|
-
result << "
|
143
|
+
result << " new value: #{path} (#{from_inspected} -> #{to_inspected})"
|
127
144
|
result << NEWLINE
|
128
145
|
end
|
129
146
|
|
130
147
|
colorize_by_line(result) do |line|
|
131
148
|
if line.start_with?("missing key:")
|
132
149
|
red(line)
|
133
|
-
elsif line.start_with?(
|
150
|
+
elsif line.start_with?(" extra key:")
|
134
151
|
green(line)
|
135
|
-
elsif line.start_with?("
|
152
|
+
elsif line.start_with?(" new value:")
|
136
153
|
yellow(line)
|
154
|
+
elsif line.start_with?("@@")
|
155
|
+
cyan(line)
|
137
156
|
else
|
138
157
|
reset_color(line)
|
139
158
|
end
|
data/lib/specdiff/differ.rb
CHANGED