nested_hash_builder 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: '03238f7b928506bf5341118b4436d3aad87ada5ba3c686b4779425ba8d0c46db'
4
+ data.tar.gz: 9dc3320dca3c8f6e2b1fe8c2f9df430ae7636110f92250db8e8e9aefdae32e48
5
+ SHA512:
6
+ metadata.gz: a92b4f9fdad9763c7304caaa141e7893f984bdc6fbc754a941891e4e0c4dc1b18387c97204cca7056d492d23afabf9b2d1c6037b35aa562480dfc44e2f047c1c
7
+ data.tar.gz: '068689310d882ca23447531ea9a2cc919ec3de80db0546e63609a3673e0be7a23a018901d41b83d10d744b4a738e10e583ad1669a6ff97f7769359dec09eedc8'
@@ -0,0 +1,43 @@
1
+ name: CI Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ 'main', 'master', 'develop' ]
6
+ pull_request:
7
+ branches: [ 'main', 'master', 'develop' ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ env:
13
+ CI: true
14
+ strategy:
15
+ matrix:
16
+ ruby-version: ['3.2', '3.3', '3.4']
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v3
20
+ - name: Install Ruby and gems
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
25
+ - name: Run unit tests
26
+ run: bundle exec rake test
27
+
28
+ typecheck_and_lint:
29
+ runs-on: ubuntu-latest
30
+ env:
31
+ CI: true
32
+ steps:
33
+ - name: Checkout code
34
+ uses: actions/checkout@v3
35
+ - name: Install Ruby and gems
36
+ uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: '3.4'
39
+ bundler-cache: true
40
+ - name: Lint Ruby files
41
+ run: bundle exec rubocop --parallel
42
+ - name: Typecheck
43
+ run: bundle exec srb tc
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ Add changes in new features here. Do not change the gem's version in pull/merge requests.
11
+
12
+ ### Changes
13
+ -
14
+
15
+ ## [0.1.0] - 2025-08-13
16
+
17
+ [Diff](https://github.com/Verseth/ruby-nested_hash_builder/compare/v0.0.0...v0.1.0)
18
+
19
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # NestedHashBuilder
2
+
3
+ This Ruby gem allows you to easily build nested Ruby Hashes.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ ```bash
10
+ bundle add nested_hash_builder
11
+ ```
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ ```bash
16
+ gem install nested_hash_builder
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ You can use the `NestedHashBuilder::build` method to easily
22
+ create nested hashed with arrays etc.
23
+
24
+ ```ruby
25
+ require 'nested_hash_builder'
26
+
27
+ hash = NestedHashBuilder.build do |h|
28
+ h.user do
29
+ h.name "John"
30
+ h.address do
31
+ h.street "123 Main St"
32
+ h.city "Anytown"
33
+ h.zip "12345"
34
+ end
35
+ h.key!("SOME:STRANGE:KEY", 2)
36
+ h.array!(:contacts) do |c|
37
+ c << h.entry! do |e|
38
+ e.email "john@example.com"
39
+ e.phone "555-1234"
40
+ end
41
+ c << h.entry! do |e|
42
+ e.email "foo@example.com"
43
+ e.phone "222-1234"
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # The resulting hash looks like:
50
+ # {
51
+ # user: {
52
+ # name: "John",
53
+ # address: {
54
+ # street: "123 Main St",
55
+ # city: "Anytown",
56
+ # zip: "12345"
57
+ # },
58
+ # "SOME:STRANGE:KEY": 2,
59
+ # contacts: [
60
+ # {
61
+ # email: "john@example.com",
62
+ # phone: "555-1234"
63
+ # },
64
+ # {
65
+ # email: "foo@example.com",
66
+ # phone: "222-1234"
67
+ # },
68
+ # ],
69
+ # }
70
+ # }
71
+ ```
72
+
73
+
74
+
75
+ ## Development
76
+
77
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
78
+
79
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
80
+
81
+ ## Contributing
82
+
83
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Verseth/nested_hash_builder.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ ::Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = ::FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ require 'rubocop/rake_task'
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test]
@@ -0,0 +1,6 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module NestedHashBuilder
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,177 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require_relative 'nested_hash_builder/version'
6
+
7
+ # A module that provides a simple DSL
8
+ # for defining complex, nested Hashes.
9
+ #
10
+ # hash = NestedHashBuilder.call do |h|
11
+ # h.price 10
12
+ # h.client do
13
+ # h.first_name 'Patrick'
14
+ # h.last_name = 'Stewart' # setter syntax is optional
15
+ # h.full_name = "#{h.local_dig!(:first_name)} #{h.local_dig!(:last_name)}"
16
+ # end
17
+ # h.inexistent = :i_wont_be_there if h.dig!(:price) == 15
18
+ # end
19
+ #
20
+ # p hash
21
+ # #=> {:price=>10, :client=>{:first_name=>"Patrick", :last_name=>"Stewart", :full_name=>"Patrick Stewart"}}
22
+ #
23
+ module NestedHashBuilder
24
+ class << self
25
+ #: (?base: Hash[untyped, untyped], ?symbolize: bool) { (Proxy) -> void } -> Hash[untyped, untyped]
26
+ def call(base: {}, symbolize: true, &block)
27
+ block.call(proxy = Proxy.new(base: base, symbolize: symbolize))
28
+ proxy.to_h
29
+ end
30
+
31
+ alias build call
32
+ end
33
+
34
+ # Object that is the receiver of all
35
+ # the building methods.
36
+ class Proxy < ::BasicObject
37
+ #: (?base: Hash[untyped, untyped], ?symbolize: bool) -> void
38
+ def initialize(base: {}, symbolize: true)
39
+ @hash = base.dup
40
+ @symbolize = symbolize
41
+ @current_parent = @hash
42
+ end
43
+
44
+ # Creates an array.
45
+ #
46
+ #: (Symbol | String) { (Array[untyped]) -> void } -> void
47
+ def ary!(name, &block)
48
+ array = []
49
+ block.call(array)
50
+ key! name, array
51
+ end
52
+
53
+ alias array! ary!
54
+
55
+ # Add a key with the specified value to the hash.
56
+ # Creates a nested hash if a block is passed.
57
+ #
58
+ #: (String | Symbol, ?top) ?{ -> void } -> void
59
+ def key!(name, value = nil, &block)
60
+ name = name.to_s.delete_suffix('=')
61
+ name = name.to_sym if @symbolize
62
+
63
+ return hash!(name, &block) if block
64
+
65
+ @current_parent[name] = value
66
+ @hash
67
+ end
68
+
69
+ #: { (Proxy) -> void } -> Hash[untyped, untyped]
70
+ def entry!(&block)
71
+ p = Proxy.new(symbolize: @symbolize)
72
+ block.call(p)
73
+ p.to_h
74
+ end
75
+
76
+ # Creates a nested hash.
77
+ #
78
+ #: (String | Symbol) -> void
79
+ def hash!(name)
80
+ previous_parent = @current_parent
81
+ @current_parent = (@current_parent[name] ||= {})
82
+ yield
83
+ @current_parent = previous_parent
84
+ end
85
+
86
+ # Gets a value of a particular key.
87
+ #
88
+ # HashBuilder.call do |h|
89
+ # h.price 10
90
+ # h.client do
91
+ # h.first_name 'Patrick'
92
+ # h.last_name 'Stewart'
93
+ # h.full_name "#{h.dig!(:client, :first_name)} #{h.dig!(:client, :last_name)}"
94
+ # h.card do
95
+ # h.number '4242424242424242'
96
+ # h.expiry_date = Time.now
97
+ # end
98
+ # end
99
+ # h.price_copy = h.dig!(:price) #=> 10
100
+ # h.card_copy = h.dig!(:client, :card, :number) #=> '4242424242424242'
101
+ # end
102
+ #
103
+ #: (*Symbol | String) -> untyped
104
+ def dig!(*names)
105
+ @hash.dig(*names)
106
+ end
107
+
108
+ # Checks if a given key is defined.
109
+ #
110
+ # HashBuilder.call do |h|
111
+ # h.price 10
112
+ # h.client do
113
+ # h.name 'Patrick Stewart'
114
+ # h.card do
115
+ # h.number '4242424242424242'
116
+ # h.expiry_date = Time.now
117
+ # end
118
+ # end
119
+ # h.price_present = h.key?(:price) #=> true
120
+ # h.card_present = h.key?(:client, :card) #=> true
121
+ # end
122
+ #
123
+ # @param names [Array<Symbol>]
124
+ #: (*Symbol | String) -> bool
125
+ def key?(*names)
126
+ !::T.unsafe(self).dig!(*names).nil?
127
+ end
128
+
129
+ # Gets a value of a particular key in the current context.
130
+ #
131
+ # HashBuilder.call do |h|
132
+ # h.price 10
133
+ # h.client do
134
+ # h.first_name 'Patrick'
135
+ # h.last_name 'Stewart'
136
+ # h.full_name "#{h.local_dig!(:first_name)} #{h.local_dig!(:last_name)}"
137
+ # end
138
+ # end
139
+ #
140
+ #: (*Symbol | String) -> untyped
141
+ def local_dig!(*names)
142
+ @current_parent.dig(*names)
143
+ end
144
+
145
+ # Checks if a given key is defined in the local context.
146
+ #
147
+ # HashBuilder.call do |h|
148
+ # h.price 10
149
+ # h.client do
150
+ # h.name 'Patrick Stewart'
151
+ # h.surname 'Stewart' if h.local_key?(:name) # true
152
+ # end
153
+ # end
154
+ #
155
+ #: (*Symbol | String) -> bool
156
+ def local_key?(*names)
157
+ !::T.unsafe(self).local_dig!(*names).nil?
158
+ end
159
+
160
+ #: -> Hash[Symbol | String, untyped]
161
+ def to_h
162
+ @hash
163
+ end
164
+
165
+ def method_missing(method_name, *args, &block)
166
+ super if method_name.end_with?('?') || method_name.end_with?('!')
167
+
168
+ key!(method_name, args.first, &block)
169
+ end
170
+
171
+ def respond_to_missing?(method_name, *_args)
172
+ return false if method_name.end_with?('?') || method_name.end_with?('!')
173
+
174
+ true
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,4 @@
1
+ module NestedHashBuilder
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nested_hash_builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mateusz Drewniak
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: sorbet-runtime
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0.5'
26
+ description: A Ruby library for building nested Hashes
27
+ email:
28
+ - m.drewniak@espago.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".github/workflows/ci.yml"
34
+ - CHANGELOG.md
35
+ - README.md
36
+ - Rakefile
37
+ - lib/nested_hash_builder.rb
38
+ - lib/nested_hash_builder/version.rb
39
+ - sig/nested_hash_builder.rbs
40
+ homepage: https://github.com/Verseth/ruby-nested_hash_builder
41
+ licenses: []
42
+ metadata:
43
+ homepage_uri: https://github.com/Verseth/ruby-nested_hash_builder
44
+ source_code_uri: https://github.com/Verseth/ruby-nested_hash_builder
45
+ changelog_uri: https://raw.githubusercontent.com/Verseth/ruby-nested_hash_builder/refs/heads/main/CHANGELOG.md
46
+ rubygems_mfa_required: 'true'
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 3.2.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.7.1
62
+ specification_version: 4
63
+ summary: A Ruby library for building nested Hashes
64
+ test_files: []