philiprehberger-differ 0.4.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +27 -0
- data/lib/philiprehberger/differ/version.rb +1 -1
- data/lib/philiprehberger/differ.rb +59 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43183caae823c98d43466bf7d1e6a2a5c993dc9f573d3d07352f3802fbc1b6d0
|
|
4
|
+
data.tar.gz: a7633ce9d9a19b69229548221475b8890198c272edf0bbb4e9d3fd3aae9eb912
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97bea10cc3793229177af29091a41a7af415ef1c383150604bffc8492a4f4dc4c0f57d78d7cbc9241c86ba3f2fa0c41c5170aafc1098f9fbe8b2e47129799b25
|
|
7
|
+
data.tar.gz: 8d728725be558d50bc31124070f43e47a7980971c75d78bc9150877bcc6dde91c52b1191514134492bae5948eff763ca70f92c4e755bb2c18d5bfb854b959bca
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-05-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Philiprehberger::Differ.invert(changeset)` — return a changeset with additions/removals swapped and changed values reversed; lets callers compute the inverse diff without rerunning the comparator
|
|
14
|
+
|
|
15
|
+
## [0.5.0] - 2026-04-21
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `Philiprehberger::Differ.stats` — structured counts of added/removed/changed paths in a changeset
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `bug_report.yml` — require Ruby version; add Gem version input per guide
|
|
22
|
+
|
|
10
23
|
## [0.4.0] - 2026-04-10
|
|
11
24
|
|
|
12
25
|
### Added
|
data/README.md
CHANGED
|
@@ -125,6 +125,17 @@ changeset = Philiprehberger::Differ.diff(old_data, new_data)
|
|
|
125
125
|
user_changes = Philiprehberger::Differ.subset(changeset, 'user')
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### Inverting a Diff
|
|
129
|
+
|
|
130
|
+
Return the inverse of a changeset — additions and removals swap, and
|
|
131
|
+
changed values are reversed. Applying the inverse undoes the original:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
forward = Philiprehberger::Differ.diff(old_data, new_data)
|
|
135
|
+
reverse = Philiprehberger::Differ.invert(forward)
|
|
136
|
+
reverse.apply(forward.apply(old_data)) # => old_data
|
|
137
|
+
```
|
|
138
|
+
|
|
128
139
|
### Three-Way Merge
|
|
129
140
|
|
|
130
141
|
```ruby
|
|
@@ -139,6 +150,20 @@ result[:conflicts] # => [{ path:, theirs:, ours: }]
|
|
|
139
150
|
Philiprehberger::Differ.breaking_changes?(changeset) # => true/false
|
|
140
151
|
```
|
|
141
152
|
|
|
153
|
+
### Change Statistics
|
|
154
|
+
|
|
155
|
+
Get a structured count summary of a changeset:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
changeset = Philiprehberger::Differ.diff(
|
|
159
|
+
{ name: 'Alice', age: 30, id: 1 },
|
|
160
|
+
{ name: 'Bob', email: 'bob@example.com' }
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
Philiprehberger::Differ.stats(changeset)
|
|
164
|
+
# => { added: 1, removed: 2, changed: 1, total: 4, paths: 4 }
|
|
165
|
+
```
|
|
166
|
+
|
|
142
167
|
## API
|
|
143
168
|
|
|
144
169
|
### `Philiprehberger::Differ.diff(old_val, new_val, ignore: [], array_key: nil)`
|
|
@@ -172,8 +197,10 @@ Returns a Float between 0.0 (completely different) and 1.0 (identical).
|
|
|
172
197
|
| Method | Description |
|
|
173
198
|
|---|---|
|
|
174
199
|
| `Differ.subset(changeset, path)` | Filter changes to a specific path prefix |
|
|
200
|
+
| `Differ.invert(changeset)` | Return a new changeset that reverses the original |
|
|
175
201
|
| `Differ.merge(base, theirs, ours)` | Three-way merge with conflict detection |
|
|
176
202
|
| `Differ.breaking_changes?(changeset)` | Detect removals and type changes |
|
|
203
|
+
| `.stats(changeset)` | Count summary of a changeset |
|
|
177
204
|
|
|
178
205
|
### `Change`
|
|
179
206
|
|
|
@@ -33,6 +33,40 @@ module Philiprehberger
|
|
|
33
33
|
Changeset.new(filtered)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Return the inverse of a changeset.
|
|
37
|
+
#
|
|
38
|
+
# Additions become removals (and vice versa), and changed entries swap
|
|
39
|
+
# their old and new values. Applying the inverted changeset to a hash
|
|
40
|
+
# that has already had the original changeset applied will undo it
|
|
41
|
+
# without keeping the prior state around.
|
|
42
|
+
#
|
|
43
|
+
# @param changeset [Changeset] the changeset to invert
|
|
44
|
+
# @return [Changeset] a new changeset with each change reversed
|
|
45
|
+
# @raise [ArgumentError] if `changeset` is not a {Changeset}
|
|
46
|
+
def self.invert(changeset)
|
|
47
|
+
raise ArgumentError, 'changeset must be a Philiprehberger::Differ::Changeset' unless changeset.is_a?(Changeset)
|
|
48
|
+
|
|
49
|
+
reversed = changeset.changes.map do |change|
|
|
50
|
+
case change.type
|
|
51
|
+
when :added
|
|
52
|
+
Change.new(path: change.path, type: :removed, old_value: change.new_value)
|
|
53
|
+
when :removed
|
|
54
|
+
Change.new(path: change.path, type: :added, new_value: change.old_value)
|
|
55
|
+
when :changed
|
|
56
|
+
Change.new(
|
|
57
|
+
path: change.path,
|
|
58
|
+
type: :changed,
|
|
59
|
+
old_value: change.new_value,
|
|
60
|
+
new_value: change.old_value
|
|
61
|
+
)
|
|
62
|
+
else
|
|
63
|
+
change
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Changeset.new(reversed)
|
|
68
|
+
end
|
|
69
|
+
|
|
36
70
|
# Perform a three-way merge with conflict detection
|
|
37
71
|
#
|
|
38
72
|
# @param base [Hash] the common ancestor
|
|
@@ -56,6 +90,31 @@ module Philiprehberger
|
|
|
56
90
|
{ merged: merged, conflicts: conflicts }
|
|
57
91
|
end
|
|
58
92
|
|
|
93
|
+
# Structured count summary of a changeset.
|
|
94
|
+
#
|
|
95
|
+
# Returns a hash of integer counts for the added, removed, and changed
|
|
96
|
+
# entries in the changeset, along with a running total and the number of
|
|
97
|
+
# unique paths. This method is read-only and never mutates the changeset.
|
|
98
|
+
#
|
|
99
|
+
# @param changeset [Changeset] the changeset to summarize
|
|
100
|
+
# @return [Hash{Symbol => Integer}] counts by kind — `{ added:, removed:, changed:, total:, paths: }`
|
|
101
|
+
# @raise [ArgumentError] if `changeset` is not a {Changeset}
|
|
102
|
+
def self.stats(changeset)
|
|
103
|
+
raise ArgumentError, 'changeset must be a Philiprehberger::Differ::Changeset' unless changeset.is_a?(Changeset)
|
|
104
|
+
|
|
105
|
+
added = changeset.added.length
|
|
106
|
+
removed = changeset.removed.length
|
|
107
|
+
changed = changeset.changed.length
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
added: added,
|
|
111
|
+
removed: removed,
|
|
112
|
+
changed: changed,
|
|
113
|
+
total: added + removed + changed,
|
|
114
|
+
paths: changeset.paths.length
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
59
118
|
# Detect if a changeset contains breaking changes (removals or type changes)
|
|
60
119
|
#
|
|
61
120
|
# @param changeset [Changeset] the changeset to check
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-differ
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A Ruby library for deep structural diffing of hashes, arrays, and nested
|
|
14
14
|
objects with apply/revert, JSON Patch output, similarity scoring, and configurable
|