otoindiff 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []