otoindiff 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d3ca434647f9c7853413d2a3cd0fd2965b763bfd50c404f583af67a98651b47b
4
+ data.tar.gz: 9a3bde8b5cbe13efc52f585b77e5bad3a57ab1dc13eae88700a162238c5c986f
5
+ SHA512:
6
+ metadata.gz: a2bae63aaa05d12a3bac9e30df8e91f98e3e871fa0d445ccb6804c8f94b5c3c4453eacfbccac675983ac29d71ef4e0a71878eb2a543e105c45e00a5d7f6c1f63
7
+ data.tar.gz: 983e9a7ccefdff0be3b9ff135a13c1a6df970c0ce1c00c5ea31e4b5a35cda5597e77aad7395ea145b5946ffe571ffcb5c097669eeb2c7e1010874efe10dd7021
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-12-12
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Otoindiff
2
+
3
+ `otoindiff` is a Ruby gem that globally enhances the behavior of Ruby's `Hash` class to support **indifferent access** by default. With `otoindiff`, you can access hash keys using either symbols or strings interchangeably without any extra steps like calling `with_indifferent_access`.
4
+
5
+ ## Features
6
+
7
+ - Seamlessly access hash keys as symbols or strings.
8
+ - Works with nested hashes and supports methods like `dig` and `fetch`.
9
+ - No need to manually call `with_indifferent_access` on hashes.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'otoindiff'
17
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
@@ -0,0 +1,23 @@
1
+ require_relative '../lib/otoindiff'
2
+ require 'benchmark'
3
+
4
+ sample_hash = {
5
+ foo: 'bar',
6
+ 'baz' => 'qux',
7
+ 'nested' => { 'level1' => { 'level2' => 'value' } }
8
+ }
9
+
10
+ Benchmark.bm do |x|
11
+ x.report('[] method (symbol key):') { 100_000.times { sample_hash[:foo] } }
12
+ x.report('[] method (string key):') { 100_000.times { sample_hash['foo'] } }
13
+ end
14
+
15
+ Benchmark.bm do |x|
16
+ x.report('dig method (nested):') { 100_000.times { sample_hash.dig('nested', 'level1', 'level2') } }
17
+ x.report('dig method (non-nested):') { 100_000.times { sample_hash.dig(:foo) } }
18
+ end
19
+
20
+ Benchmark.bm do |x|
21
+ x.report('fetch method (symbol key):') { 100_000.times { sample_hash.fetch(:foo) } }
22
+ x.report('fetch method (string key):') { 100_000.times { sample_hash.fetch('foo') } }
23
+ end
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'otoindiff'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'irb'
11
+ IRB.start(__FILE__)
data/bin/release ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'rake/testtask'
6
+ require 'bundler/gem_tasks'
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs << 'test'
10
+ t.test_files = FileList['test/**/*_test.rb']
11
+ t.verbose = true
12
+ end
13
+
14
+ desc 'Run tests and release the gem'
15
+ task release: [:test] do
16
+ system('gem build otoindiff.gemspec')
17
+
18
+ version = `grep -m1 'version =' otoindiff.gemspec`.split("'")[1]
19
+
20
+ if system("gem push otoindiff-#{version}.gem")
21
+ puts "Successfully released otoindiff version #{version}"
22
+ system("rm otoindiff-#{version}.gem")
23
+ else
24
+ puts 'Failed to push gem'
25
+ exit 1
26
+ end
27
+ end
28
+
29
+ task default: :release
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Otoindiff
4
+ VERSION = '0.1.0'
5
+ end
data/lib/otoindiff.rb ADDED
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'otoindiff/version'
4
+
5
+ require 'active_support'
6
+ require 'active_support/core_ext/hash/indifferent_access'
7
+
8
+ # Extends Ruby's Hash class with more flexible key access methods
9
+ #
10
+ # This extension provides enhanced hash key lookup capabilities:
11
+ # - Allows interchangeable use of string and symbol keys
12
+ # - Provides more forgiving key access methods
13
+ # - Maintains original Hash behavior while adding convenience
14
+ #
15
+ # @example Flexible key access
16
+ # hash = { foo: 'bar', 'baz' => 'qux' }
17
+ # hash[:foo] # => 'bar'
18
+ # hash['foo'] # => 'bar'
19
+ # hash.dig('baz', :key) # Nested key access with mixed key types
20
+ #
21
+ # @note This extension is designed to be non-intrusive and backwards compatible
22
+ class Hash
23
+ alias original_brackets []
24
+ alias original_dig dig
25
+ alias original_fetch fetch
26
+
27
+ # Retrieve a hash value with flexible key type conversion
28
+ #
29
+ # @param key [Object] The key to retrieve
30
+ # @return [Object, nil] The value associated with the key, or nil if not found
31
+ def [](key)
32
+ return original_brackets(key) if key?(key)
33
+
34
+ if key.is_a?(String) || key.is_a?(Symbol)
35
+ original_brackets(key.to_s) || original_brackets(key.to_sym)
36
+ else
37
+ original_brackets(key)
38
+ end
39
+ end
40
+
41
+ # Dig into a nested hash with flexible key type conversion
42
+ #
43
+ # @param keys [Array<Object>] Path of keys to traverse
44
+ # @return [Object, nil] The nested value, or nil if path is invalid
45
+ def dig(*keys)
46
+ keys.reduce(self) do |current, key|
47
+ break nil unless current.is_a?(Hash)
48
+
49
+ if key.is_a?(String) || key.is_a?(Symbol)
50
+ current.original_brackets(key) ||
51
+ current.original_brackets(key.to_s) ||
52
+ current.original_brackets(key.to_sym)
53
+ else
54
+ current.original_brackets(key)
55
+ end
56
+ end
57
+ end
58
+
59
+ # Fetch a hash value with flexible key type conversion and error handling
60
+ #
61
+ # @param key [Object] The key to retrieve
62
+ # @param args [Array] Optional default value or block
63
+ # @return [Object] The value associated with the key
64
+ # @raise [KeyError] If key is not found and no default is provided
65
+ def fetch(key, *args, &block)
66
+ return original_fetch(key, *args, &block) if key?(key)
67
+
68
+ if key.is_a?(String) || key.is_a?(Symbol)
69
+ begin
70
+ original_fetch(key.to_s, *args, &block)
71
+ rescue KeyError
72
+ original_fetch(key.to_sym, *args, &block)
73
+ end
74
+ else
75
+ original_fetch(key, *args, &block)
76
+ end
77
+ end
78
+ end
data/sig/otoindiff.rbs ADDED
@@ -0,0 +1,31 @@
1
+ module Otoindiff
2
+ VERSION: String
3
+
4
+ # Extended methods for Hash class with flexible key access
5
+ module HashExtensions
6
+ # Retrieve a value with flexible key type conversion
7
+ #
8
+ # @param key [String | Symbol | Object] The key to retrieve
9
+ # @return [Object?] The value associated with the key, or nil if not found
10
+ def [](key: String | Symbol | Object): Object?
11
+
12
+ # Dig into a nested hash with flexible key type conversion
13
+ #
14
+ # @param keys [Array<String | Symbol | Object>] Path of keys to traverse
15
+ # @return [Object?] The nested value, or nil if path is invalid
16
+ def dig(*keys: String | Symbol | Object): Object?
17
+
18
+ # Fetch a hash value with flexible key type conversion
19
+ #
20
+ # @param key [String | Symbol | Object] The key to retrieve
21
+ # @param default [Object?] Optional default value
22
+ # @param block [Proc?] Optional block for default value computation
23
+ # @return [Object] The value associated with the key
24
+ # @raise [KeyError] If key is not found and no default is provided
25
+ def fetch(
26
+ key: String | Symbol | Object,
27
+ default: Object?,
28
+ &block: -> Object?
29
+ ): Object
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: otoindiff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bugloper
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-12-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '8.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '8.0'
27
+ description: The otoindiff gem modifies all Ruby Hash objects to support indifferent
28
+ access by default, allowing seamless use of string or symbol keys interchangeably.
29
+ email:
30
+ - bugloper@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.md
36
+ - README.md
37
+ - Rakefile
38
+ - benchmarks/oto_benchmarks.rb
39
+ - bin/console
40
+ - bin/release
41
+ - bin/setup
42
+ - lib/otoindiff.rb
43
+ - lib/otoindiff/version.rb
44
+ - sig/otoindiff.rbs
45
+ homepage: https://github.com/bugloper/otoindiff
46
+ licenses: []
47
+ metadata:
48
+ allowed_push_host: https://rubygems.org
49
+ homepage_uri: https://github.com/bugloper/otoindiff
50
+ source_code_uri: https://github.com/bugloper/otoindiff
51
+ changelog_uri: https://github.com/bugloper/otoindiff/blob/main/CHANGELOG.md
52
+ rubygems_mfa_required: 'false'
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.5.11
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Enhances Ruby hashes with default indifferent access.
72
+ test_files: []