tag_options 0.9.3 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb408340ce9d19af1ea31d33d3589bf341cead0595fae98905f183370e27fe5e
4
- data.tar.gz: 40f0b893d5a513bf5d4a15a66e984ba2d331cfe674832b408b28a09f3f2da073
3
+ metadata.gz: 5802916aaa57253843bc9fa02aed005828d4ffa30a8ccf514b9949c5c0859d2d
4
+ data.tar.gz: 80ee0b00682ee8d94595039cd1569c0152d0dedb225a78672986416118bec6fe
5
5
  SHA512:
6
- metadata.gz: 88bee13cbc9235499350735d61299f9aa0f06db4227200f07c93708672133b7e7bd378871376fa0c221405aeb8487df85b60b1aede65356ef48383a31ab75529
7
- data.tar.gz: f883c852b8704e940e59c43f067aaac197ba1bf6857977b1676a76e5d1b155cbe7aa13e300a4c55dad2bac466b3468cbc787a68869d777398306e7bd9f7cf3bf
6
+ metadata.gz: f498f2aa015310a8b4d3800dcb435c5b946665f5f53bf54630a80666e3c56f05c54b75788ea84b2d57829d2369404408621a4b2b7adeec1c5668fad2e61ac37a
7
+ data.tar.gz: 9e04caa1298cec89d34af81daa7c11b51c18020c3bb013aaf7b104de5f1e5c80d545aeaae334929d3c2796d5fa00f31aafb59028b588bf9aefd218278e56bb9c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.1.0] - 2023-03-01
6
+
7
+ - Switched to inheriting from ActiveSupport::HashWithIndifferentAccess.
8
+ - Added before/after/around initialize callback support.
9
+
10
+ ## [1.0.0] - 2022-06-14
11
+
12
+ - Rewrote and simplified TagOptions::Hash and supporting classes.
13
+ - BREAKING CHANGES, read documentation for updated usage before updating.
14
+
5
15
  ## [0.9.3] - 2021-11-11
6
16
 
7
17
  - Added TagOptions::Hash() ease-of-use method
data/README.md CHANGED
@@ -1,17 +1,22 @@
1
1
  # Tag Options
2
2
 
3
- Simple library for manipulating options passed to the Rails `tag`, `content_tag`, and other tag helpers.
3
+ [![Test](https://github.com/wamonroe/tag_options/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/wamonroe/tag_options/actions/workflows/test.yml)
4
4
 
5
- This library provides a simple class to make authoring resuable helpers and [View Components](https://viewcomponent.org)
6
- easier when you want to allow for the input of properties on HTML elements, but also need to add/set your own.
5
+ Simple library for manipulating options passed to the Rails `tag`,
6
+ `content_tag`, and other tag helpers.
7
7
 
8
- `TagOptions::Hash` is an object that normalizes the options passed to Rails helper, while providing helpful methods
9
- to manipulate the values of HTML properties:
8
+ This library provides a simple class to make authoring resuable helpers and
9
+ [View Components](https://viewcomponent.org) easier when you want to allow for
10
+ the input of properties on HTML elements, but also need to add/set your own.
11
+
12
+ `TagOptions::Hash` is an object that normalizes the options passed to Rails
13
+ helper, while providing helpful methods to manipulate the values of HTML
14
+ properties:
10
15
 
11
16
  ```ruby
12
17
  def external_link_to(name, url, options={})
13
18
  options = TagOptions::Hash.new(options)
14
- options.combine_with_class!('external-link')
19
+ options.at(:class).combine!("external-link")
15
20
  link_to(name, url, options)
16
21
  end
17
22
  ```
@@ -19,7 +24,7 @@ end
19
24
  Called with:
20
25
 
21
26
  ```ruby
22
- external_link_to('Example', 'https://example.com', class: 'ml-2')
27
+ external_link_to("Example", "https://example.com", class: "ml-2")
23
28
  ```
24
29
 
25
30
  Would render:
@@ -30,24 +35,24 @@ Would render:
30
35
 
31
36
  ## Table of Contents
32
37
 
33
- - [Installation](#installation)
34
- - [Configuration](#configuration)
35
- - [General Usage](#general-usage)
36
- - [combine_with!](#combinewith)
37
- - [override!](#override)
38
- - [Conditional Usage](#conditional-usage)
39
- - [Custom Property Handling](#custom-property-handling)
40
- - [Development](#development)
41
- - [Contributing](#contributing)
42
- - [To Do](#to-do)
43
- - [License](#license)
38
+ - [Tag Options](#tag-options)
39
+ - [Table of Contents](#table-of-contents)
40
+ - [Installation](#installation)
41
+ - [General Usage](#general-usage)
42
+ - [combine!](#combine)
43
+ - [set!](#set)
44
+ - [Conditional Usage](#conditional-usage)
45
+ - [Custom Property Resolvers](#custom-property-resolvers)
46
+ - [Development](#development)
47
+ - [Contributing](#contributing)
48
+ - [License](#license)
44
49
 
45
50
  ## Installation
46
51
 
47
52
  Add this line to your application's Gemfile:
48
53
 
49
54
  ```ruby
50
- gem 'tag_options'
55
+ gem "tag_options"
51
56
  ```
52
57
 
53
58
  And then execute:
@@ -56,33 +61,6 @@ And then execute:
56
61
  bundle install
57
62
  ```
58
63
 
59
- ## Configuration
60
-
61
- Generate an initializer to customize the default configuration:
62
-
63
- ```sh
64
- rails generate arc_options:install
65
- ```
66
-
67
- ```ruby
68
- TagOptions.configure do |config|
69
- # fallback_property_handler
70
- #
71
- # Defines the default behavior of how values are treated on HTML properties. `TagOptions::PropertyHandler::Generic`
72
- # allows for multiple, unique, values seperated by spaces.
73
- config.fallback_property_handler = 'TagOptions::PropertyHandler::Generic'
74
-
75
- # property_handlers
76
- #
77
- # Allows of the custom handling of HTML properties that match the defined property handler. Properties are handled by
78
- # the first matching property handler.
79
- config.property_handlers = [
80
- 'TagOptions::PropertyHandler::Singular',
81
- 'TagOptions::PropertyHandler::Style'
82
- ]
83
- end
84
- ```
85
-
86
64
  ## General Usage
87
65
 
88
66
  Initialize a `TagOptions::Hash` directly or by passing an existing `Hash`.
@@ -91,155 +69,137 @@ Initialize a `TagOptions::Hash` directly or by passing an existing `Hash`.
91
69
  TagOptions::Hash.new
92
70
  => {}
93
71
 
94
- hash = {class: 'flex'}
72
+ hash = {class: "flex"}
95
73
  TagOptions::Hash.new(hash)
96
74
  => {:class=>"flex"}
97
75
  ```
98
76
 
99
- ### combine_with!
77
+ ### combine!
100
78
 
101
- Combine HTML attributes with an existing `TagOptions::Hash` using `combine_with!`
79
+ Combine HTML attributes with an existing `TagOptions::Hash` by chaining `at` and
80
+ `combine!`
102
81
 
103
82
  ```ruby
104
- options = TagOptions::Hash.new(class: 'flex')
105
- options.combine_with!(class: 'mt-1')
83
+ options = TagOptions::Hash.new(class: "flex")
84
+ options.at(:class).combine!("mt-1")
106
85
  => {:class=>"flex mt-1"}
107
86
  ```
108
87
 
109
88
  Values can also be specified as arrays.
110
89
 
111
90
  ```ruby
112
- options = TagOptions::Hash.new(class: 'flex')
113
- options.combine_with!(class: ['mt-1', 'mx-2'])
91
+ options = TagOptions::Hash.new(class: "flex")
92
+ options.at(:class).combine!(["mt-1", "mx-2"])
114
93
  => {:class=>"flex mt-1 mx-2"}
115
94
  ```
116
95
 
117
96
  HTML attributes are only added if they don't already exist.
118
97
 
119
98
  ```ruby
120
- options = TagOptions::Hash.new(class: 'flex')
121
- options.combine_with!(class: 'flex flex-col')
99
+ options = TagOptions::Hash.new(class: "flex")
100
+ options.at(:class).combine!("flex flex-col")
122
101
  => {:class=>"flex flex-col"}
123
102
  ```
124
103
 
125
- You can also combine multiple HTML attributes in one operation.
104
+ You can also combine values on nested hashes.
126
105
 
127
106
  ```ruby
128
- options = TagOptions::Hash.new(class: 'flex')
129
- options.combine_with!(class: 'mt-1', 'data-controller': 'dropdown')
130
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
107
+ options = TagOptions::Hash.new(class: "flex", data: {controller: "dropdown"})
108
+ options.at(:data, :controller).combine!("toggle")
109
+ => {:class=>"flex", :data=>{:controller=>"dropdown toggle"}
131
110
  ```
132
111
 
133
- Dash seperated HTML attributes, such as `data-controller` can also be specified as nested hashes.
112
+ If a nested hash doesn't already exist it will be automatically added.
134
113
 
135
114
  ```ruby
136
- options = TagOptions::Hash.new(class: 'flex')
137
- options.combine_with!(class: 'mt-1', data: { controller: 'dropdown' })
138
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
115
+ options = TagOptions::Hash.new(class: "flex")
116
+ options.at(:data, :controller).combine!("dropdown")
117
+ => {:class=>"flex", :data=>{:controller=>"dropdown"}
139
118
  ```
140
119
 
141
- Dash seperated HTML attributes can also be specified using underscores.
142
-
143
- ```ruby
144
- options = TagOptions::Hash.new(class: 'flex')
145
- options.combine_with!(class: 'mt-1', data_controller: 'dropdown')
146
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
147
- ```
120
+ ### set!
148
121
 
149
- For ease of use, you can also combine values into a single HTML attribute using `combine_with_<attribute>!`, where
150
- `<attribute>` is the name of the HTML attribute. Dash seperated HTML attributes should be specified using underscores.
122
+ Chaining `at` and `set!` functions nearly the same as `combine!` with all same
123
+ usage patterns. The major difference is that the set method will override any
124
+ existing values.
151
125
 
152
126
  ```ruby
153
- options = TagOptions::Hash.new(class: 'flex')
154
- options.combine_with_class! 'mt-1'
155
- options.combine_with_data_controller! 'dropdown'
156
- => {:class=>"flex mt-1", :"data-controller"=>"dropdown"}
127
+ options = TagOptions::Hash.new(class: "flex")
128
+ options.at(:class).set!("block")
129
+ => {:class=>"block"}
157
130
  ```
158
131
 
159
- ### override!
160
-
161
- The method `override!` functions nearly the same as `combine_with!` with all same usage patterns, including ease of
162
- use methods, such as `override_class!`. The major difference is that the override methods will not combine specified
163
- values with existing values. Any specified values will override any existing values.
164
-
165
132
  ## Conditional Usage
166
133
 
167
- Both the `combine_with!` and `override!` allow for values to be conditionally added to HTML attributes using an argument
168
- array. Where the values are added unconditionally and key/value pairs have their key added _IF_ the value is true.
134
+ Both the `combine!` and `set!` allow for values to be conditionally added to
135
+ HTML attributes using an argument array. Where the values are added
136
+ unconditionally and key/value pairs have their key added _IF_ the value is true.
169
137
 
170
138
  ```ruby
171
139
  # assuming `centered?` returns `true`
172
- options = TagOptions::Hash.new(class: 'flex')
173
- options.combine_with!(class: ['mt-1', 'mx-auto': centered?, 'mx-2': !centered?])
140
+ options = TagOptions::Hash.new(class: "flex")
141
+ options.at(:class).combine!("mt-1", "mx-auto": centered?, "mx-2": !centered?)
174
142
  => {:class=>"flex mt-1 mx-auto"}
175
143
  ```
176
144
 
177
- The syntax of the `combine_with_<attribute>!` and `override_<attribute>!` ease of use method allow for values and
178
- conditional values to be combined more naturally using a more typical argument pattern.
145
+ ## Custom Property Resolvers
179
146
 
180
- ```ruby
181
- # assuming `centered?` returns `true`
182
- options = TagOptions::Hash.new(class: 'flex')
183
- options.combine_with_class!('mt-1', 'mx-auto': centered?, 'mx-2': !centered?)
184
- => {:class=>"flex mt-1 mx-auto"}
185
- ```
147
+ Chaining `at` to `combine!` or `set!` processes HTML properties similar to
148
+ `class`.
186
149
 
187
- ## Custom Property Handling
150
+ - Multiple are allowed
151
+ - Multiple values are seperated by a space
152
+ - Duplicate values are not added
188
153
 
189
- Tag Options ships with several property handlers enabled by default.
154
+ Tag Options also ships with a special `style` resolver, which can be used by
155
+ pass `as: :style` to `at`.
190
156
 
191
- - `TagOptions::PropertyHandler::Generic` - Processes all HTML properties that don't match another property handler.
192
- Handles HTML properties similar to `class`.
193
- - Multiple unique values are allowed
194
- - Multiple values are seperated by a space
195
- - `TagOptions::PropertyHandler::Singular` - Processes `id`, `role`, and `aria-*` properties.
196
- - Both `combine_with!` and `override!` function the same on these attributes.
197
- - The last specified/resolved value is assigned to the property.
198
- - `TagOptions::PropertyHandler::Style` - Processes the `style` property. Allows for the parsing of HTML style property.
199
- - Multiple values are allowed
200
- - Values must be specified as `property: value;`
201
- - The `combine_with!` method will overwrite an existing style property if it exists, but leave the reamining style
202
- properties untouched.
203
- - The `override!` method will overwrite all existing style properties.
204
-
205
- Tag Options also ships with an optional property handler for sorting Tailwind CSS classes inspired by/based on the
206
- VS Code extension [Headwind](https://github.com/heybourn/headwind). To enable this optional property handler,
207
- [generate a configuration](#configuration) and then add it as a property handler:
157
+ - Multiple values are allowed
158
+ - Values must be specified as `property: value;`
159
+ - Duplicate `property: value;` pairs are not added
160
+ - The `combine!` method will overwrite an existing style property if it exists,
161
+ add properties that don't exist, and leave the remaining properties untouched.
162
+ - The `set!` method will overwrite all existing style properties.
208
163
 
209
164
  ```ruby
210
- require 'tag_options/property_handler/tailwind_css'
165
+ options = TagOptions::Hash.new(style: "display: block; margin-left: 0;")
166
+ options.at(:style, as: :style).combine!("display: flex; margin-right: 0;")
167
+ => {:style=>"display: flex; margin-left: 0; margin-right: 0;"}
168
+ ```
169
+
170
+ A `TagOptions::Resolver` class is available if you wish to implement your own
171
+ custom handlers. For examples on doing so, see the [built-in
172
+ handlers](https://github.com/wamonroe/tag_options/tree/main/lib/tag_options/resolvers).
211
173
 
174
+ To register a custom handler:
175
+
176
+ ```ruby
177
+ # config/initializers/tag_options.rb
212
178
  TagOptions.configure do |config|
213
- config.property_handlers = [
214
- 'TagOptions::PropertyHandler::TailwindCSS',
215
- 'TagOptions::PropertyHandler::Singular',
216
- 'TagOptions::PropertyHandler::Style'
217
- ]
179
+ config.register_resolver :custom, "MyCustomResolver"
218
180
  end
219
181
  ```
220
182
 
221
- A `TagOptions::PropertyHandler::Base` class is available if you wish to implement your own custom handlers. For
222
- examples on doing so, see the
223
- [built-in handlers](https://github.com/wamonroe/tag_options/tree/main/lib/tag_options/property_handler).
224
-
225
183
  ## Development
226
184
 
227
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can
228
- also run:
185
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
186
+ `bin/rspec` to run the tests. You can also run:
229
187
 
230
188
  - `bin/console` for an interactive prompt that will allow you to experiment
231
189
  - `bin/rubocop` to run RuboCop to check the code style and formatting
232
- - `bin/update_tailwindcss` to update the sort order from the latest [Headwind](https://github.com/heybourn/headwind)
233
- configuration.
234
190
 
235
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
236
- version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
237
- push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
191
+ To build this gem on your local machine, run `bundle exec rake build`. To
192
+ release a new version, update the version number in `version.rb`, and then run
193
+ `bundle exec rake release`, which will create a git tag for the version, push
194
+ git commits and the created tag, and push the `.gem` file to
195
+ [rubygems.org](https://rubygems.org).
238
196
 
239
197
  ## Contributing
240
198
 
241
- Bug reports and pull requests are welcome on GitHub at https://github.com/wamonroe/tag_options.
199
+ Bug reports and pull requests are welcome on GitHub at
200
+ https://github.com/wamonroe/tag_options.
242
201
 
243
202
  ## License
244
203
 
245
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
204
+ The gem is available as open source under the terms of the [MIT
205
+ License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup'
3
+ require "bundler/setup"
4
4
 
5
- require 'bundler/gem_tasks'
6
- require 'rake/testtask'
5
+ require "bundler/gem_tasks"
6
+ require "rake/testtask"
7
7
 
8
8
  Rake::TestTask.new(:test) do |t|
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
9
+ t.libs << "test"
10
+ t.pattern = "test/**/*_test.rb"
11
11
  t.verbose = false
12
12
  end
13
13
 
@@ -1,4 +1,6 @@
1
- # frozen_string_literal: true
1
+ require "tag_options/resolvers/default"
2
+ require "tag_options/resolvers/style"
3
+ require "tag_options/errors/resolver_error"
2
4
 
3
5
  module TagOptions
4
6
  class << self
@@ -11,24 +13,25 @@ module TagOptions
11
13
  def configure
12
14
  yield(configuration)
13
15
  end
14
-
15
- def reset_configuration
16
- @configuration = TagOptions::Configuration.new
17
- end
18
16
  end
19
17
 
20
18
  class Configuration
21
- attr_writer :fallback_property_handler, :property_handlers
19
+ def initialize
20
+ @resolvers = {
21
+ default: "TagOptions::Resolvers::Default",
22
+ style: "TagOptions::Resolvers::Style"
23
+ }
24
+ end
22
25
 
23
- def fallback_property_handler
24
- @fallback_property_handler ||= 'TagOptions::PropertyHandler::Generic'
26
+ def resolver(name)
27
+ unless (resolver_name = @resolvers[name])
28
+ raise TagOptions::Errors::ResolverError, name
29
+ end
30
+ Object.const_get(resolver_name)
25
31
  end
26
32
 
27
- def property_handlers
28
- @property_handlers ||= [
29
- 'TagOptions::PropertyHandler::Singular',
30
- 'TagOptions::PropertyHandler::Style'
31
- ]
33
+ def register_resolver(name, class_name)
34
+ @resolvers[name] = class_name
32
35
  end
33
36
  end
34
37
  end
@@ -0,0 +1,4 @@
1
+ module TagOptions
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ require "tag_options/error"
2
+
3
+ module TagOptions
4
+ module Errors
5
+ class NotHashError < Error
6
+ def initialize(key, *keys, type:)
7
+ hash_path = [key, *keys].join("=>")
8
+ super("unsupport type `#{type}` is already located at #{hash_path}")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require "tag_options/error"
2
+
3
+ module TagOptions
4
+ module Errors
5
+ class ResolverError < Error
6
+ def initialize(name)
7
+ super("a resolver named `#{name}` has not been registered")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,140 +1,40 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/string'
4
- require 'active_support/core_ext/array'
5
- require 'forwardable'
6
- require 'tag_options/property_handler/resolve_value'
1
+ require "active_support/callbacks"
2
+ require "active_support/core_ext/hash/indifferent_access"
3
+ require "tag_options/hash_at"
4
+ require "tag_options/errors/not_hash_error"
7
5
 
8
6
  module TagOptions
9
- class Hash
10
- extend Forwardable
11
-
12
- def_delegators :@data, :inspect, :to_h, :to_hash, :to_s, :stringify_keys, :<, :<=, :==, :>, :>=
13
-
14
- # Hashes passed into the initializer are automatically flattened, with nested keys seperated by dashes. For example,
15
- # `data: { controller: 'dropdown' }`` becomes `'data-controller': 'dropdown'`.
16
- def initialize(hash={})
17
- @data = {}
18
- flatten_hash(hash).each do |property, value|
19
- self[property] = value
20
- end
21
- end
22
-
23
- # []
24
- # Underscores in a property name is automatically coverted to dashes. Properties can be specified as strings or
25
- # symbols, both return the same value.
26
- def [](property)
27
- @data[normalize_property(property)]
28
- end
29
-
30
- # []=
31
- # Hashes assigned to a property are automatically flatten, with nested keys seperated by dashes. Underscores in a
32
- # property name are automatically converted to dashes. Propertiess can be specified as strings or symbols, both will
33
- # assign the value to the same property.
34
- def []=(property, value)
35
- if value.is_a?(::Hash)
36
- flatten_hash({ property => value }).each do |flat_property, flat_value|
37
- store(flat_property, flat_value)
7
+ class Hash < ActiveSupport::HashWithIndifferentAccess
8
+ include ActiveSupport::Callbacks
9
+ define_callbacks :initialize
10
+
11
+ def initialize(hash = {})
12
+ run_callbacks :initialize do
13
+ hash.each do |key, value|
14
+ self[key] = value.is_a?(::Hash) ? self.class.new(value) : value
38
15
  end
39
- else
40
- store(property, value)
41
16
  end
42
17
  end
43
18
 
44
- # combine_with!
45
- # Allows you to combine values with multiple HTML attributes in one operation. Passed keys should be the HTML
46
- # property to combine the values with. The values can be specified as single string (e.g. `class: 'flex'`) or as an
47
- # argument array (e.g. `class: ['flex', 'mt-2', 'flex-col': layout_column?]`). Hashes in an argument array have
48
- # their keys combined only their value is true. Nested keys will automatically be flattened and combine with the
49
- # associated property (e.g. `data: { controller: 'dropdown' }` would be combined with `data-controller`).
50
- #
51
- # #combine_with!(
52
- # class: ['flex', 'mt-2', 'flex-col': layout_column?],
53
- # data: {
54
- # controller: ['dropdown', 'navbar': navbar?]
55
- # }
56
- # )
57
- #
58
- # TagOptions::Hash also responses to combine_with_<name>!, where `<name>` is the name of the HTML attribute to
59
- # combine the passed argument array with. If `<name>` is specified with a value containing underscores, the HTML
60
- # attribute is converted to dashes, for example: `combine_with_data_controller!` will result in the argument array
61
- # being combined with existing values in `data-controller`.
62
- def combine_with!(hash={})
63
- flatten_hash(hash).each do |property, args|
64
- store(property, self[property], *args)
65
- end
66
- self
19
+ def at(key, *nested_keys, as: :default)
20
+ TagOptions::HashAt.new(opt_hash: self, keys: [key, *nested_keys], as: as)
67
21
  end
68
22
 
69
- # override!
70
- # Allows you to override values on multiple HTML properties in one operation. Passed keys should be the HTML
71
- # properties to override the values of. The values can be passed as single string (e.g. `class: 'flex'`) or as an
72
- # argument array (e.g. `class: ['flex', 'mt-2', 'flex-col': layout_column?]`). Hashes in an argument array have
73
- # their keys added only if their value is true. Nested keys will automatically be flattened and override the value
74
- # at the associated property (e.g. `data: { controller: 'dropdown' }` would override values at `data-controller`).
75
- #
76
- # #override!(
77
- # class: ['flex', 'mt-2', 'flex-col': layout_column?],
78
- # data: {
79
- # controller: ['dropdown', 'navbar': navbar?]
80
- # }
81
- # )
82
- #
83
- # TagOptions::Hash also responses to override_<name>!, where `<name>` is the name of the HTML attribute to override
84
- # the passed argument array on. If `<name>` is specified with a value containing underscores, the resulting HTML
85
- # attribute is automatically nested, for example `override_data_controller!` will result in the argument array
86
- # overriding the existing values in `data-controller`.
87
- def override!(hash={})
88
- flatten_hash(hash).each do |property, args|
89
- store(property, *args)
90
- end
91
- self
23
+ def dig(*keys)
24
+ keys.size.zero? ? self : super
92
25
  end
93
26
 
94
- private
95
-
96
- def action_matcher
97
- /\A(?<action>combine_with|override)_(?<property>.*)!\z/
98
- end
99
-
100
- def flatten_hash(hash)
101
- hash.each_with_object({}) do |(property, value), result|
102
- if value.is_a?(::Hash)
103
- flatten_hash(value).map do |nested_property, nested_value|
104
- result["#{property}-#{nested_property}".to_sym] = nested_value
105
- end
106
- else
107
- result[property] = value
27
+ def populate!(*keys)
28
+ populated_keys = []
29
+ data = self
30
+ keys.each do |key|
31
+ data[key] ||= TagOptions::Hash.new
32
+ data = data[key]
33
+ unless data.is_a?(TagOptions::Hash)
34
+ raise TagOptions::Errors::NotHashError.new(populated_keys, type: data.class)
108
35
  end
109
36
  end
110
- end
111
-
112
- def method_missing(method_name, *args, &block)
113
- match_data = action_matcher.match(method_name.to_s)
114
- if match_data
115
- public_send("#{match_data['action']}!", { match_data['property'] => args }, &block)
116
- else
117
- super
118
- end
119
- end
120
-
121
- def store(property, *values)
122
- property = normalize_property(property)
123
- conditions = values.extract_options!
124
- value = resolve_value(property, *values, **conditions)
125
- value.empty? ? @data.delete(property) : @data[property] = value
126
- end
127
-
128
- def resolve_value(property, *values, **conditions)
129
- TagOptions::PropertyHandler::ResolveValue.call(property, *values, **conditions)
130
- end
131
-
132
- def normalize_property(property)
133
- property.to_s.downcase.dasherize.to_sym
134
- end
135
-
136
- def respond_to_missing?(method_name, include_private=false)
137
- action_matcher.match?(method_name.to_s) || super
37
+ self
138
38
  end
139
39
  end
140
40
  end
@@ -0,0 +1,30 @@
1
+ require "tag_options/configuration"
2
+
3
+ module TagOptions
4
+ class HashAt
5
+ def initialize(opt_hash:, keys:, as:)
6
+ @opt_hash = opt_hash
7
+ @keys = keys[..-2]
8
+ @value_key = keys[-1]
9
+ @resolver = TagOptions.configuration.resolver(as)
10
+ end
11
+
12
+ def combine!(*values, **conditions)
13
+ @opt_hash.populate!(*@keys)
14
+ current_value = @opt_hash.dig(*@keys, @value_key)
15
+ set_value! @resolver.call(current_value, *values, **conditions)
16
+ end
17
+
18
+ def set!(*values, **conditions)
19
+ @opt_hash.populate!(*@keys)
20
+ set_value! @resolver.call(*values, **conditions)
21
+ end
22
+
23
+ private
24
+
25
+ def set_value!(value)
26
+ @opt_hash.dig(*@keys)[@value_key] = value
27
+ @opt_hash
28
+ end
29
+ end
30
+ end