philiprehberger-dot_access 0.7.0 → 0.8.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: c6904bd424f71dc389f5dc2ac6c89e12c85b00fb3da9e0d3a8188e74441d7f12
4
- data.tar.gz: adfd54811267a34f7b84c922df8f7f3c460250dd1b77b7013a212999846cbc2a
3
+ metadata.gz: c067bfe9e48161257e318e8504f10a2604d9899e4340dadbfc42a76f8df23466
4
+ data.tar.gz: ccf282fefd6897e5ae90bf664fffb8efda891d67a357bf226a2fdd1a3af79cce
5
5
  SHA512:
6
- metadata.gz: 8d92a2e22c4cf47787560bb89843c23e915debc51649b52e658703d33edf0608f5bc446dc398e6984af0418f56695a0da670dc572d439e5cd6f60047802f87be
7
- data.tar.gz: 6ee9cc79cd31fb360f38fd55db44306025d961746c571ee8919a5bf1beaa27fd9b6da89c8164d7665fe9f576bfdfc6df8b14ae8ee8cc20c31399c8f3debe8471
6
+ metadata.gz: e0f6ce17f68d1685a8742327c215a5af5a14879cf4a9560eccd20bf127d06a04fd99a81acfc0e15ae895836c3a87c8c5034e615336f255a8313ee09977deff4d
7
+ data.tar.gz: 31f94f7fa8ee543f7ee9304bd3814ef24cc30b7199108821ec61c4da4b6e54c9983c7d8fb0024dae94aace73c06017275019e9febcf7e88cdd7c2c5f77bfbb52
data/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-05-20
11
+
12
+ ### Added
13
+ - JSON Pointer (RFC 6901) support: `Wrapper#get_pointer`, `#set_pointer`, `#delete_pointer`, and `#has_pointer?` plus the helper `Wrapper.parse_pointer`, for interop with JSON Schema and JSON Patch tooling
14
+ - Card image reference in the README for registry-side rendering
15
+
16
+ ### Changed
17
+ - Raised the `Metrics/ClassLength` RuboCop ceiling on `Wrapper` to fit the new pointer methods
18
+
10
19
  ## [0.7.0] - 2026-04-30
11
20
 
12
21
  ### Added
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-dot_access.svg)](https://rubygems.org/gems/philiprehberger-dot_access)
5
5
  [![Last updated](https://img.shields.io/github/last-commit/philiprehberger/rb-dot-access)](https://github.com/philiprehberger/rb-dot-access/commits/main)
6
6
 
7
+ ![philiprehberger-dot_access](https://raw.githubusercontent.com/philiprehberger/rb-dot-access/main/package-card.webp)
8
+
7
9
  Dot-notation accessor for nested hashes with nil-safe traversal
8
10
 
9
11
  ## Requirements
@@ -230,6 +232,28 @@ config = Philiprehberger::DotAccess.wrap({ a: { b: 1 } })
230
232
  config.to_h # => { a: { b: 1 } }
231
233
  ```
232
234
 
235
+ ### JSON Pointer (RFC 6901) Access
236
+
237
+ Useful when interoperating with JSON Schema, JSON Patch, or any tooling that already speaks JSON Pointer paths.
238
+
239
+ ```ruby
240
+ data = Philiprehberger::DotAccess.wrap(
241
+ users: [{ name: "alice" }, { name: "bob" }],
242
+ "a/b": { "c~d": 1 }
243
+ )
244
+
245
+ data.get_pointer("/users/0/name") # => "alice"
246
+ data.get_pointer("") # => the wrapper itself (root)
247
+ data.get_pointer("/missing", default: :nf) # => :nf
248
+
249
+ # ~1 escapes /, ~0 escapes ~ — so the key "a/b" → "a~1b"
250
+ data.get_pointer("/a~1b/c~0d") # => 1
251
+
252
+ data.has_pointer?("/users/1/name") # => true
253
+ updated = data.set_pointer("/users/0/name", "alex")
254
+ trimmed = data.delete_pointer("/users/0/name")
255
+ ```
256
+
233
257
  ## API
234
258
 
235
259
  ### `Philiprehberger::DotAccess`
@@ -263,6 +287,10 @@ config.to_h # => { a: { b: 1 } }
263
287
  | `#to_json` | Serialize back to a JSON string |
264
288
  | `#to_yaml` | Serialize back to a YAML string |
265
289
  | `#to_h` | Convert back to a plain hash |
290
+ | `#get_pointer(pointer, default: nil)` | Retrieve a value by a JSON Pointer (RFC 6901) path |
291
+ | `#has_pointer?(pointer)` | Check if a JSON Pointer path exists |
292
+ | `#set_pointer(pointer, value)` | Return a new wrapper with the value set at a JSON Pointer path |
293
+ | `#delete_pointer(pointer)` | Return a new wrapper without the value at a JSON Pointer path |
266
294
 
267
295
  ### `Philiprehberger::DotAccess::NullAccess`
268
296
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module DotAccess
5
- VERSION = '0.7.0'
5
+ VERSION = '0.8.0'
6
6
  end
7
7
  end
@@ -257,6 +257,100 @@ module Philiprehberger
257
257
  Wrapper.new(new_data)
258
258
  end
259
259
 
260
+ # Access a value by a JSON Pointer (RFC 6901) path.
261
+ #
262
+ # JSON Pointer paths start with `/`, separate segments with `/`, and escape
263
+ # `~` as `~0` and `/` as `~1`. An empty pointer (`""`) refers to the whole
264
+ # wrapped document.
265
+ #
266
+ # @param pointer [String] the JSON Pointer (e.g. `"/users/0/name"`)
267
+ # @param default [Object] value returned if the pointer is not found
268
+ # @return [Object] the value at the pointer, or ``default``
269
+ def get_pointer(pointer, default: nil)
270
+ segments = Wrapper.parse_pointer(pointer)
271
+ return self if segments.empty?
272
+
273
+ result = segments.reduce(@data) do |current, seg|
274
+ case current
275
+ when Hash then current[seg.to_sym]
276
+ when Array
277
+ index = array_index(seg, current)
278
+ return default if index.nil?
279
+
280
+ current[index]
281
+ else return default
282
+ end
283
+ end
284
+
285
+ result.nil? ? default : result
286
+ end
287
+
288
+ # Whether a JSON Pointer (RFC 6901) path exists in the wrapped structure.
289
+ #
290
+ # @param pointer [String] the JSON Pointer
291
+ # @return [Boolean]
292
+ def has_pointer?(pointer)
293
+ segments = Wrapper.parse_pointer(pointer)
294
+ return true if segments.empty?
295
+
296
+ current = @data
297
+ segments.each do |seg|
298
+ case current
299
+ when Hash
300
+ return false unless current.key?(seg.to_sym)
301
+
302
+ current = current[seg.to_sym]
303
+ when Array
304
+ index = array_index(seg, current)
305
+ return false if index.nil?
306
+
307
+ current = current[index]
308
+ else
309
+ return false
310
+ end
311
+ end
312
+ true
313
+ end
314
+
315
+ # Set a value at a JSON Pointer path, returning a new Wrapper.
316
+ #
317
+ # @param pointer [String] the JSON Pointer
318
+ # @param value [Object] the value to set
319
+ # @return [Wrapper] a new wrapper with the updated value
320
+ # @raise [Error] if the pointer is empty (would replace the entire document)
321
+ def set_pointer(pointer, value)
322
+ segments = Wrapper.parse_pointer(pointer)
323
+ raise Error, 'cannot set the root pointer' if segments.empty?
324
+
325
+ Wrapper.new(deep_set(to_h, segments, value))
326
+ end
327
+
328
+ # Delete the value at a JSON Pointer path, returning a new Wrapper.
329
+ #
330
+ # @param pointer [String] the JSON Pointer
331
+ # @return [Wrapper] a new wrapper without the specified pointer
332
+ # @raise [Error] if the pointer is empty
333
+ def delete_pointer(pointer)
334
+ segments = Wrapper.parse_pointer(pointer)
335
+ raise Error, 'cannot delete the root pointer' if segments.empty?
336
+
337
+ Wrapper.new(deep_delete(to_h, segments))
338
+ end
339
+
340
+ # Parse a JSON Pointer string into an array of unescaped segments.
341
+ # Public so callers and {#get_pointer}/{#set_pointer}/etc. share one parser.
342
+ #
343
+ # @param pointer [String]
344
+ # @return [Array<String>] the unescaped segments
345
+ # @raise [Error] if the pointer is not a String or does not start with `/`
346
+ def self.parse_pointer(pointer)
347
+ raise Error, 'pointer must be a String' unless pointer.is_a?(String)
348
+ return [] if pointer.empty?
349
+ raise Error, "JSON Pointer must start with '/' (got #{pointer.inspect})" unless pointer.start_with?('/')
350
+
351
+ pointer[1..].split('/', -1).map { |seg| seg.gsub('~1', '/').gsub('~0', '~') }
352
+ end
353
+
260
354
  # Flatten the nested structure into a hash whose keys are dot-paths.
261
355
  #
262
356
  # @return [Hash] flat hash where keys are dot-path strings
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-dot_access
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.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-30 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Access deeply nested hash values using dot notation (config.database.host)
14
14
  with nil-safe traversal that never raises on missing keys. Supports path-based get/set,