philiprehberger-differ 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 501adf50f677014ce90da4bc0222ca212f6a36d091e6612e1a93a3feab001867
4
- data.tar.gz: 28e1bf4f0c0f662f7c8f7d19cb22c24271c709f45a22d7646f86857e586764ff
3
+ metadata.gz: f6f6c2e127be0912f009924625aa0ef3babcc21855bb9a6f0af744f6c6dff179
4
+ data.tar.gz: c2c99a65ed6ad72b399f52c99a3ac5866ca0c7705917c395124281c570869d31
5
5
  SHA512:
6
- metadata.gz: 6aaaf8bb2c8717bcba57ee14cad6386113c20c815d270fc135da8991dbfc3fa3796389e4bee2ccf1c79b63b443dc926218f4a55a7eca8748a4005a501a3be0ad
7
- data.tar.gz: 7a70fb5815b3a8eb3a6150a521a130ea2c795298d97e522a14bba4bc58c18845199ea3c06cc3797914cad6d476357c2fd86ef89627763805226eac9add84e4cf
6
+ metadata.gz: 9e3c7e4dcfabefe06c2348f4e13a1fa3023234ac3e57c2e8c3ac51437027944c906bfe3bc721e57d259087381bba09ee734181b80a83ce69872e6be814d1e041
7
+ data.tar.gz: 6796e028cb9a84341db6fc7952e79c692bbcd8d87dbb1af116da04fdd883785db8c63a0e80909940355aae65f993b31437eb69aaa2ab27805f372ed69841ea4c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-04-21
11
+
12
+ ### Added
13
+ - `Philiprehberger::Differ.stats` — structured counts of added/removed/changed paths in a changeset
14
+
15
+ ### Fixed
16
+ - `bug_report.yml` — require Ruby version; add Gem version input per guide
17
+
18
+ ## [0.4.0] - 2026-04-10
19
+
20
+ ### Added
21
+ - `Changeset` now includes `Enumerable` for direct iteration over changes
22
+ - `Changeset#count` returning the number of changes
23
+ - `Changeset#paths` returning all changed paths
24
+ - `Changeset#include?(path)` to check if a specific path was changed
25
+ - `Changeset#summary` returning `{ added:, removed:, changed: }` counts
26
+
10
27
  ## [0.3.0] - 2026-04-01
11
28
 
12
29
  ### Added
data/README.md CHANGED
@@ -139,6 +139,20 @@ result[:conflicts] # => [{ path:, theirs:, ours: }]
139
139
  Philiprehberger::Differ.breaking_changes?(changeset) # => true/false
140
140
  ```
141
141
 
142
+ ### Change Statistics
143
+
144
+ Get a structured count summary of a changeset:
145
+
146
+ ```ruby
147
+ changeset = Philiprehberger::Differ.diff(
148
+ { name: 'Alice', age: 30, id: 1 },
149
+ { name: 'Bob', email: 'bob@example.com' }
150
+ )
151
+
152
+ Philiprehberger::Differ.stats(changeset)
153
+ # => { added: 1, removed: 2, changed: 1, total: 4, paths: 4 }
154
+ ```
155
+
142
156
  ## API
143
157
 
144
158
  ### `Philiprehberger::Differ.diff(old_val, new_val, ignore: [], array_key: nil)`
@@ -154,6 +168,11 @@ Returns a Float between 0.0 (completely different) and 1.0 (identical).
154
168
  | Method | Description |
155
169
  |---|---|
156
170
  | `changed?` | Returns `true` if any differences exist |
171
+ | `count` | Number of changes |
172
+ | `paths` | Array of all changed paths |
173
+ | `include?(path)` | Check if a specific path was changed |
174
+ | `summary` | `{ added:, removed:, changed: }` counts |
175
+ | `each` | Iterate over changes (includes `Enumerable`) |
157
176
  | `changes` | All `Change` objects |
158
177
  | `added` | Changes where `type == :added` |
159
178
  | `removed` | Changes where `type == :removed` |
@@ -169,6 +188,7 @@ Returns a Float between 0.0 (completely different) and 1.0 (identical).
169
188
  | `Differ.subset(changeset, path)` | Filter changes to a specific path prefix |
170
189
  | `Differ.merge(base, theirs, ours)` | Three-way merge with conflict detection |
171
190
  | `Differ.breaking_changes?(changeset)` | Detect removals and type changes |
191
+ | `.stats(changeset)` | Count summary of a changeset |
172
192
 
173
193
  ### `Change`
174
194
 
@@ -3,12 +3,52 @@
3
3
  module Philiprehberger
4
4
  module Differ
5
5
  class Changeset
6
+ include Enumerable
7
+
6
8
  attr_reader :changes
7
9
 
8
10
  def initialize(changes = [])
9
11
  @changes = changes
10
12
  end
11
13
 
14
+ # Iterate over each Change.
15
+ #
16
+ # @yield [Change] each change
17
+ # @return [Enumerator] if no block given
18
+ def each(&)
19
+ @changes.each(&)
20
+ end
21
+
22
+ # Number of changes.
23
+ #
24
+ # @return [Integer]
25
+ def count
26
+ @changes.length
27
+ end
28
+
29
+ # Array of all changed paths.
30
+ #
31
+ # @return [Array<String>]
32
+ def paths
33
+ @changes.map(&:path)
34
+ end
35
+
36
+ # Check if a specific path was changed.
37
+ #
38
+ # @param path [String, Symbol] the path to check
39
+ # @return [Boolean]
40
+ def include?(path)
41
+ path_s = path.to_s
42
+ @changes.any? { |c| c.path.to_s == path_s }
43
+ end
44
+
45
+ # Summary counts by change type.
46
+ #
47
+ # @return [Hash{Symbol => Integer}] { added:, removed:, changed: }
48
+ def summary
49
+ { added: added.length, removed: removed.length, changed: changed.length }
50
+ end
51
+
12
52
  def changed?
13
53
  !@changes.empty?
14
54
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module Differ
5
- VERSION = '0.3.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -56,6 +56,31 @@ module Philiprehberger
56
56
  { merged: merged, conflicts: conflicts }
57
57
  end
58
58
 
59
+ # Structured count summary of a changeset.
60
+ #
61
+ # Returns a hash of integer counts for the added, removed, and changed
62
+ # entries in the changeset, along with a running total and the number of
63
+ # unique paths. This method is read-only and never mutates the changeset.
64
+ #
65
+ # @param changeset [Changeset] the changeset to summarize
66
+ # @return [Hash{Symbol => Integer}] counts by kind — `{ added:, removed:, changed:, total:, paths: }`
67
+ # @raise [ArgumentError] if `changeset` is not a {Changeset}
68
+ def self.stats(changeset)
69
+ raise ArgumentError, 'changeset must be a Philiprehberger::Differ::Changeset' unless changeset.is_a?(Changeset)
70
+
71
+ added = changeset.added.length
72
+ removed = changeset.removed.length
73
+ changed = changeset.changed.length
74
+
75
+ {
76
+ added: added,
77
+ removed: removed,
78
+ changed: changed,
79
+ total: added + removed + changed,
80
+ paths: changeset.paths.length
81
+ }
82
+ end
83
+
59
84
  # Detect if a changeset contains breaking changes (removals or type changes)
60
85
  #
61
86
  # @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.3.0
4
+ version: 0.5.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-04-01 00:00:00.000000000 Z
11
+ date: 2026-04-21 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