tag_options 0.9.3 → 1.1.0

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