rb-optionsresolver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +381 -0
- data/lib/optionsresolver/exceptions/invalid_options.rb +5 -0
- data/lib/optionsresolver/exceptions/invalid_parameter.rb +5 -0
- data/lib/optionsresolver/exceptions/missing_options.rb +5 -0
- data/lib/optionsresolver/exceptions/undefined_options.rb +5 -0
- data/lib/optionsresolver/optionsresolver.rb +242 -0
- data/lib/optionsresolver/utils/hash_ext.rb +9 -0
- data/lib/rb-optionsresolver.rb +1 -0
- data/rb-optionsresolver.gemspec +12 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 64f1580fdc0be144d8b75ac0083dbf605879cd91d32424cfc48aa0b0a548203e
|
4
|
+
data.tar.gz: 224c3d263123c0c571ccf360081b6dbce951ae2ff9d2fd5bec1caa009de37977
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3da84da48bf6f4372e0d9e918d44495ed0b11e49a7c9bc9f84f1a56156cbeb904b1840da178f8d0a06a6eb71c2d018b23dc894ef98cbcaa6e41b3f1f8b5fe58e
|
7
|
+
data.tar.gz: 2be3435849f85b0a826ec0f2777dbf0e4a1ac53b62c181ca5926a1772955b4513b3ed183acab90bdddd675307bd0e8ae50451a7a5e23302ce41d9de8fc701f89
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 NamNV609
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
# Rb OptionsResolver - Symfony OptionsResolver for Ruby
|
2
|
+
|
3
|
+
> The Rb OptionsResolver library is Symfony OptionsResolver for Ruby. It allows you to create an options system with required options, defaults, validation (type, value), normalization and more.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```
|
8
|
+
$ gem install rb-optionsresolver
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
To use OptionsResolver:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require "rb-optionsresolver"
|
17
|
+
```
|
18
|
+
|
19
|
+
Imagine you have a `Mailer` class which has four options: `host`, `username`, `password` and `port`:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Mailer
|
23
|
+
def initialize options
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
When accessing the `@options`, you need to add a lot of boilerplate code to check which options are set:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class Mailer
|
33
|
+
# ...
|
34
|
+
|
35
|
+
def send_mail from, to
|
36
|
+
mail = ...
|
37
|
+
mail.set_host @options[:host] ? @options[:host] : "smtp.example.com"
|
38
|
+
mail.set_username @options[:username] ? @options[:username] : "user"
|
39
|
+
mail.set_password @options[:password] ? @options[:password] : "pa$$word"
|
40
|
+
mail.set_port @options[:port] ? @options[:port] : 25
|
41
|
+
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
This boilerplate is hard to read and repetitive. Also, the default values of the options are buried in the business logic of your code. Use code below to fix that:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class Mailer
|
51
|
+
def initialize options
|
52
|
+
default_options = {
|
53
|
+
host: "smtp.example.com",
|
54
|
+
username: "user"
|
55
|
+
}
|
56
|
+
|
57
|
+
@options = [*default_options, *options].to_h
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
Now all four options are guaranteed to be set. But what happens if the user of the `Mailer` class makes a mistake?
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
mailer_opts = {
|
66
|
+
usernme: "johndoe" # usernme misspelled (instead of username)
|
67
|
+
}
|
68
|
+
mailer = Mailer.new mailer_opts
|
69
|
+
```
|
70
|
+
|
71
|
+
No error will be shown. In the best case, the bug will appear during testing, but the developer will spend time looking for the problem. In the worst case, the bug might not appear until it's deployed to the live system.
|
72
|
+
|
73
|
+
Fortunately, the `OptionsResolver` class helps you to fix this problem:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class Mailer
|
77
|
+
def initialize options
|
78
|
+
resolver = OptionsResolver.new
|
79
|
+
resolver.set_defaults({
|
80
|
+
host: "smtp.example.com",
|
81
|
+
username: "user",
|
82
|
+
password: "pa$$word",
|
83
|
+
port: 25
|
84
|
+
})
|
85
|
+
|
86
|
+
@options = resolver.resolve options
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Like before, all options will be guaranteed to be set. Additionally, an `UndefinedOptions` is thrown if an unknown option is passed:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
mailer_opts = {usernme: "johndoe"}
|
95
|
+
mailer = Mailer.new mailer_opts
|
96
|
+
|
97
|
+
# The option "usernme" does not exist.
|
98
|
+
# Know options are: "host", "username", "password", "port" (UndefinedOptions)
|
99
|
+
```
|
100
|
+
|
101
|
+
The rest of your code can access the values of the options without boilerplate code:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class Mailer
|
105
|
+
# ...
|
106
|
+
|
107
|
+
def send_mail from, to
|
108
|
+
mail = ...
|
109
|
+
|
110
|
+
mail.set_host @options[:host]
|
111
|
+
mail.set_username @options[:username]
|
112
|
+
mail.set_password @options[:password]
|
113
|
+
mail.set_port @options[:port]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
### Required Options
|
119
|
+
|
120
|
+
If an option must be set by the caller, pass that option to `set_required()`. For example, to make the `host` option required, you can do:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class Mailer
|
124
|
+
def initialize options
|
125
|
+
resolver = OptionsResolver.new
|
126
|
+
resolver.set_required "host"
|
127
|
+
|
128
|
+
@options = resolver.resolve options
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
If you omit a required option, a `MissingOptions` will be thrown:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
mailer = Mailer.new
|
137
|
+
|
138
|
+
# The required options "host" is missing. (MissingOptions)
|
139
|
+
```
|
140
|
+
|
141
|
+
The `set_required()` method accepts a single name or an array of option names if you have more than one required option:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Mailer
|
145
|
+
# ...
|
146
|
+
resolver.set_required %w(host username password)
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
Use `is_required?()` to find out if an option is required. You can use `get_required_options()` to retrieve the names of all required options:
|
151
|
+
|
152
|
+
```
|
153
|
+
required_options = resolver.get_required_options()
|
154
|
+
```
|
155
|
+
|
156
|
+
If you want to check whether a required option is still missing from the default options, you can use `is_missing?()`. The difference between this and `is_required?()` is that this method will return false if a required option has already been set:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# ...
|
160
|
+
resolver.is_required? "host" # true
|
161
|
+
resolver.is_missing? "host" # true
|
162
|
+
resolver.set_default "host", "smtp.example.com"
|
163
|
+
resolver.is_required? "host" # true
|
164
|
+
resolver.is_missing? "host" # false
|
165
|
+
```
|
166
|
+
|
167
|
+
The method `get_missing_options()` lets you access the names of all missing options.
|
168
|
+
|
169
|
+
### Type Validation
|
170
|
+
|
171
|
+
You can run additional checks on the options to make sure they were passed correctly. To validate the types of the options, call `set_allowed_types()`:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# ...
|
175
|
+
# specify one allowed type
|
176
|
+
resolver.set_allowed_types "port", "int"
|
177
|
+
```
|
178
|
+
|
179
|
+
> **TODO**: Specify multiple allowed types and can pass fully qualified class names.
|
180
|
+
|
181
|
+
You can pass any type for which an:
|
182
|
+
|
183
|
+
* `integer` (`int`)
|
184
|
+
* `string` (`str`)
|
185
|
+
* `array` (`arr`)
|
186
|
+
* `boolean` (`bool`)
|
187
|
+
* `float`
|
188
|
+
* `hash`
|
189
|
+
* `symbol` (`sym`)
|
190
|
+
* `range`
|
191
|
+
* `regexp`
|
192
|
+
* `proc`
|
193
|
+
|
194
|
+
If you pass an invalid option now, an `InvalidOptions` is thrown:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
mailer_opts = {
|
198
|
+
port: "465"
|
199
|
+
}
|
200
|
+
mailer = Mailer.new mailer_opts
|
201
|
+
|
202
|
+
# The option "port" with "465" is expected to be of type "int" (InvalidOptions)
|
203
|
+
```
|
204
|
+
|
205
|
+
> **TODO**: In sub-classes, you can use `add_allowed_types()` to add additional allowed types without erasing the ones already set.
|
206
|
+
|
207
|
+
### Value Validation
|
208
|
+
|
209
|
+
Some options can only take one of a fixed list of predefined values. For example, suppose the `Mailer` class has a `transport` option which can be one of `sendmail`, `mail` and `smtp`. Use the method `set_allowed_values()` to verify that the passed option contains one of these values:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
class Mailer
|
213
|
+
# ...
|
214
|
+
resolver.set_default("transport", "sendmail")
|
215
|
+
.set_allowed_values("transport", %w(sendmail mail smtp))
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
If you pass an invalid transport, an `InvalidOptions` is thrown:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
mailer_opts = {
|
223
|
+
transport: "send-mail"
|
224
|
+
}
|
225
|
+
mailer = Mailer.new mailer_opts
|
226
|
+
|
227
|
+
# The option "transport" with value "send-mail" is invalid.
|
228
|
+
# Accepted values are "sendmail", "mail", "smtp" (RuntimeError)
|
229
|
+
```
|
230
|
+
|
231
|
+
For options with more complicated validation schemes, pass a proc (or lambda) which returns `true` for acceptable values and `false` for invalid values:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
# ...
|
235
|
+
resolver.set_allowed_values "transport", Proc.new{|transport|
|
236
|
+
# return true or false
|
237
|
+
%w(sendmail mail smtp).include? transport
|
238
|
+
}
|
239
|
+
```
|
240
|
+
|
241
|
+
> **TODO**: In sub-classes, you can use `add_allowed_values()` to add additional allowed values without erasing the ones already set.
|
242
|
+
|
243
|
+
### Option Normalization
|
244
|
+
|
245
|
+
|
246
|
+
Sometimes, option values need to be normalized before you can use them. For instance, assume that the `host` should always start with `http://`. To do that, you can write normalizers. Normalizers are executed after validating an option. You can configure a normalizer by calling `set_normailizer()`:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# ...
|
250
|
+
resolver.set_normailizer "host", lambda{|options, host|
|
251
|
+
host = "http://#{host}" unless /^https?\:\/\//.match? host
|
252
|
+
host
|
253
|
+
}
|
254
|
+
```
|
255
|
+
|
256
|
+
The normalizer receives the actual `host` and returns the normalized form. You see that the proc (or lambda) also takes an `options` parameter. This is useful if you need to use other options during normalization:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
# ...
|
260
|
+
.set_normalizer("host", Proc.new{|options, host|
|
261
|
+
unless /^https?\:\/\//.match? host
|
262
|
+
if options["encryption"] == "ssl"
|
263
|
+
host = "https://#{host}"
|
264
|
+
else
|
265
|
+
host = "http://#{host}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
host
|
270
|
+
})
|
271
|
+
```
|
272
|
+
|
273
|
+
### Default Values that Depend on another Option
|
274
|
+
|
275
|
+
Suppose you want to set the default value of the `port` option based on the encryption chosen by the user of the `Mailer` class. More precisely, you want to set the port to `465` if SSL is used and to `25` otherwise.
|
276
|
+
|
277
|
+
You can implement this feature by passing a proc (or lambda) as the default value of the `port` option. The proc (or lambda) receives the options as argument. Based on these options, you can return the desired default value:
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
# ...
|
281
|
+
resolver.set_default("encryption", nil)
|
282
|
+
.set_default("port", lambda{|options, _| options["encryption"] == "ssl" ? 465 :25})
|
283
|
+
```
|
284
|
+
|
285
|
+
> The argument of the callable must be type hinted as `options`. Otherwise, the callable itself is considered as the default value of the option.
|
286
|
+
|
287
|
+
> The proc (or lambda) is only executed if the `port` option isn't set by the user or overwritten in a sub-class.
|
288
|
+
|
289
|
+
A previously set default value can be accessed by adding a second argument to the proc (or lambda):
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
resolver.set_defaults({
|
293
|
+
encryption: nil,
|
294
|
+
host: "example.org"
|
295
|
+
}).set_default("host", Proc.new{|options, previous_host_value|
|
296
|
+
options["encryption"] == "ssl" ? "secure.example.org" : previous_host_value
|
297
|
+
})
|
298
|
+
```
|
299
|
+
|
300
|
+
As seen in the example, this feature is mostly useful if you want to reuse the default values set in parent classes in sub-classes.
|
301
|
+
|
302
|
+
### Options without Default Values
|
303
|
+
|
304
|
+
In some cases, it is useful to define an option without setting a default value. This is useful if you need to know whether or not the user _actually_ set an option or not. For example, if you set the default value for an option, it's not possible to know whether the user passed this value or if it simply comes from the default:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
class Mailer
|
308
|
+
def initialize options
|
309
|
+
resolver = OptionsResolver.new
|
310
|
+
resolver.set_default("port", 25)
|
311
|
+
|
312
|
+
@options = resolver.resolve options
|
313
|
+
end
|
314
|
+
|
315
|
+
def send_mail from, to
|
316
|
+
# Is this the default value or did the caller of the class really
|
317
|
+
# set the port to 25?
|
318
|
+
|
319
|
+
if @options["port"] == 25
|
320
|
+
# ...
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
```
|
325
|
+
|
326
|
+
You can use `set_defined()` to define an option without setting a default value. Then the option will only be included in the resolved options if it was actually passed to `resolve()`:
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
class Mailer
|
330
|
+
def initialize options
|
331
|
+
resolver = OptionsResolver.new
|
332
|
+
resolver.set_defined "port"
|
333
|
+
|
334
|
+
@options = resolver.resolve options
|
335
|
+
end
|
336
|
+
|
337
|
+
def send_mail from = nil, to = nil
|
338
|
+
if @options["port"]
|
339
|
+
puts "Set!"
|
340
|
+
else
|
341
|
+
puts "Not set"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
mailer_opts = {}
|
347
|
+
mailer = Mailer.new mailer_opts
|
348
|
+
mailer.send_mail
|
349
|
+
# => Not set!
|
350
|
+
|
351
|
+
mailer_opts = {port: 25}
|
352
|
+
mailer = Mailer.new mailer_opts
|
353
|
+
mailer.send_mail
|
354
|
+
# => Set!
|
355
|
+
```
|
356
|
+
|
357
|
+
You can also pass an array of option names if you want to define multiple options in one go:
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
resolver.set_defined %w(port encryption)
|
361
|
+
```
|
362
|
+
|
363
|
+
The methods `is_defined?()` and `get_defined_options()` let you find out which options are defined:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
# ...
|
367
|
+
if resolver.is_defined? "host"
|
368
|
+
# One of the following was called:
|
369
|
+
# resolver.set_default "host", ...
|
370
|
+
# resolver.set_required "host"
|
371
|
+
# resolver.set_defined "host"
|
372
|
+
end
|
373
|
+
|
374
|
+
defined_options = resolver.get_defined_options
|
375
|
+
```
|
376
|
+
|
377
|
+
That's it! You now have all the tools and knowledge needed to easily process options in your code.
|
378
|
+
|
379
|
+
# Credits
|
380
|
+
|
381
|
+
Original documentation for PHP: [https://symfony.com/doc/3.4/components/options_resolver.html](https://symfony.com/doc/3.4/components/options_resolver.html)
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require "optionsresolver/exceptions/invalid_parameter"
|
2
|
+
require "optionsresolver/exceptions/undefined_options"
|
3
|
+
require "optionsresolver/exceptions/missing_options"
|
4
|
+
require "optionsresolver/exceptions/invalid_options"
|
5
|
+
require "optionsresolver/utils/hash_ext"
|
6
|
+
|
7
|
+
class OptionsResolver
|
8
|
+
def initialize
|
9
|
+
@defined_options = []
|
10
|
+
@required_options = []
|
11
|
+
@default_values = {}
|
12
|
+
@allowed_types = {}
|
13
|
+
@allowed_values = {}
|
14
|
+
@normalizers = {}
|
15
|
+
@previous_default_values = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_defined defined_keys
|
19
|
+
if defined_keys.is_a?(Array)
|
20
|
+
@defined_options.concat defined_keys
|
21
|
+
else
|
22
|
+
@defined_options.push defined_keys
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_defined_options
|
29
|
+
@defined_options
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_defined? option_key
|
33
|
+
@defined_options.include? option_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_required required_keys
|
37
|
+
if required_keys.is_a?(Array)
|
38
|
+
@required_options.concat required_keys
|
39
|
+
@defined_options.concat required_keys
|
40
|
+
else
|
41
|
+
@required_options.push required_keys
|
42
|
+
@defined_options.push required_keys
|
43
|
+
end
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_required_options
|
49
|
+
@required_options
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_required? option_key
|
53
|
+
@required_options.include? option_key
|
54
|
+
end
|
55
|
+
|
56
|
+
def is_missing? option_key
|
57
|
+
!@default_values.include? option_key
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_missing_options
|
61
|
+
@required_options.map do |required_key|
|
62
|
+
next if @default_values.include?(required_key)
|
63
|
+
required_key
|
64
|
+
end.compact
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_default option_key, default_value
|
68
|
+
@previous_default_values[option_key] = @default_values[option_key] if
|
69
|
+
@default_values.include? option_key
|
70
|
+
|
71
|
+
@default_values[option_key] = default_value
|
72
|
+
self.set_defined option_key
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_defaults option_default_values
|
78
|
+
raise InvalidParameter unless option_default_values.is_a? Hash
|
79
|
+
|
80
|
+
option_default_values.each do |opt_key, opt_value|
|
81
|
+
opt_key = opt_key.to_s
|
82
|
+
|
83
|
+
self.set_default(opt_key, opt_value).set_defined opt_key
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_default_values option_key = nil
|
90
|
+
return @default_values unless option_key
|
91
|
+
|
92
|
+
@default_values[option_key]
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_allowed_types option_key, option_value_types
|
96
|
+
@allowed_types[option_key] = option_value_types
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_allowed_types option_key = nil
|
102
|
+
return @allowed_types unless option_key
|
103
|
+
|
104
|
+
@allowed_types[option_key]
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_allowed_values option_key, option_allowed_value
|
108
|
+
@allowed_values[option_key] = option_allowed_value
|
109
|
+
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_allowed_values option_key = nil
|
114
|
+
return @allowed_values unless option_key
|
115
|
+
|
116
|
+
@allowed_values
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_normalizer option_key, normalizer_value
|
120
|
+
@normalizers[option_key] = normalizer_value
|
121
|
+
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def resolve data_object
|
126
|
+
raise InvalidParameter unless data_object.is_a? Hash
|
127
|
+
|
128
|
+
# Unique @defined_options
|
129
|
+
@defined_options.uniq!
|
130
|
+
# Stringify hash keys
|
131
|
+
data_object = data_object.stringify_keys
|
132
|
+
# Check undefined options
|
133
|
+
check_undefined_options data_object.keys
|
134
|
+
# Set default values
|
135
|
+
data_object = set_options_default data_object
|
136
|
+
# Check missing required options
|
137
|
+
check_missing_required_options data_object
|
138
|
+
# Check invalid options type
|
139
|
+
check_invalid_options_type data_object
|
140
|
+
# Check invalid options value
|
141
|
+
check_invalid_options_value data_object
|
142
|
+
# Normalizer options value
|
143
|
+
data_object = normalizer_options_value data_object
|
144
|
+
|
145
|
+
data_object
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def check_undefined_options option_keys
|
150
|
+
know_options_str = "\"#{@defined_options.join("\", \"")}\""
|
151
|
+
|
152
|
+
option_keys.each do |opt_key|
|
153
|
+
raise UndefinedOptions, "The option \"#{opt_key}\" does not exist. Know options are: #{know_options_str}" unless
|
154
|
+
@defined_options.include? opt_key.to_s
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_options_default data_object
|
159
|
+
@default_values.each do |option_key, default_value|
|
160
|
+
next unless data_object[option_key].nil?
|
161
|
+
|
162
|
+
previous_default_value = @previous_default_values[option_key]
|
163
|
+
data_object[option_key] = default_value.is_a?(Proc) ? default_value.call(data_object, previous_default_value) : default_value
|
164
|
+
end
|
165
|
+
|
166
|
+
data_object
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_missing_required_options data_object
|
170
|
+
@required_options.each do |required_key|
|
171
|
+
next if data_object[required_key.to_s]
|
172
|
+
|
173
|
+
raise MissingOptions, "The required options \"#{required_key}\" is missing."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def check_invalid_options_type data_object
|
178
|
+
@allowed_types.each do |option_key, allowed_type|
|
179
|
+
option_key = option_key.to_s
|
180
|
+
option_value = data_object[option_key]
|
181
|
+
|
182
|
+
case allowed_type.downcase
|
183
|
+
when "int", "integer"
|
184
|
+
throw_invalid_options_exception option_key, option_value, "int" unless option_value.is_a? Integer
|
185
|
+
when "str", "string"
|
186
|
+
throw_invalid_options_exception option_key, option_value, "str" unless option_value.is_a? String
|
187
|
+
when "arr", "array"
|
188
|
+
throw_invalid_options_exception option_key, option_value, "array" unless option_value.is_a? Array
|
189
|
+
when "bool", "boolean"
|
190
|
+
throw_invalid_options_exception option_key, option_value, "boolean" unless [true, false].include? option_value
|
191
|
+
when "float"
|
192
|
+
throw_invalid_options_exception option_key, option_value, "float" unless option_value.is_a? Float
|
193
|
+
when "hash"
|
194
|
+
throw_invalid_options_exception option_key, option_value, "hash" unless option_value.is_a? Hash
|
195
|
+
when "sym", "symbol"
|
196
|
+
throw_invalid_options_exception option_key, option_value, "symbol" unless option_value.is_a? Symbol
|
197
|
+
when "range"
|
198
|
+
throw_invalid_options_exception option_key, option_value, "range" unless option_value.is_a? Range
|
199
|
+
when "regexp"
|
200
|
+
throw_invalid_options_exception option_key, option_value, "regexp" unless option_value.is_a? Regexp
|
201
|
+
when "proc"
|
202
|
+
throw_invalid_options_exception option_key, option_value, "proc" unless option_value.is_a? Proc
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def check_invalid_options_value data_object
|
208
|
+
@allowed_values.each do |option_key, allowed_value|
|
209
|
+
option_key = option_key.to_s
|
210
|
+
option_value = data_object[option_key]
|
211
|
+
is_valid_value = true
|
212
|
+
accepted_value_msg = ""
|
213
|
+
|
214
|
+
if allowed_value.is_a? Array
|
215
|
+
is_valid_value = allowed_value.include? option_value
|
216
|
+
accepted_value_msg = " Accepted values are \"#{allowed_value.join("\", \"")}\""
|
217
|
+
elsif allowed_value.is_a? Proc
|
218
|
+
is_valid_value = allowed_value.call option_value
|
219
|
+
else
|
220
|
+
is_valid_value = (option_value == allowed_value)
|
221
|
+
end
|
222
|
+
|
223
|
+
raise "The option \"#{option_key}\" with value \"#{option_value}\" is invalid.#{accepted_value_msg}" unless is_valid_value
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def normalizer_options_value data_object
|
228
|
+
@normalizers.each do |option_key, normalizer_method|
|
229
|
+
raise InvalidParameter "Normalizer for key \"#{option_key}\" has invalid method." unless normalizer_method.is_a? Proc
|
230
|
+
|
231
|
+
option_value = data_object[option_key]
|
232
|
+
data_object[option_key] = normalizer_method.call data_object, option_value
|
233
|
+
end
|
234
|
+
|
235
|
+
data_object
|
236
|
+
end
|
237
|
+
|
238
|
+
def throw_invalid_options_exception key, val, expected_type
|
239
|
+
msg = "The option \"#{key}\" with \"#{val}\" is expected to be of type \"#{expected_type}\""
|
240
|
+
raise InvalidOptions, msg
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "optionsresolver/optionsresolver"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "rb-optionsresolver"
|
3
|
+
spec.version = "0.0.1"
|
4
|
+
spec.authors = ["NamNV609"]
|
5
|
+
spec.email = ["namnv609@gmail.com"]
|
6
|
+
spec.description = "Ruby library like Symfony OptionsResolver"
|
7
|
+
spec.summary = "Allows to create an options system with required options, defaults, validation (type, value), normalization and more."
|
8
|
+
spec.license = "MIT"
|
9
|
+
spec.homepage = "https://github.com/namnv609/rb-optionsresolver"
|
10
|
+
spec.files = `git ls-files`.split($/)
|
11
|
+
spec.require_paths = ["lib"]
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rb-optionsresolver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- NamNV609
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ruby library like Symfony OptionsResolver
|
14
|
+
email:
|
15
|
+
- namnv609@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- lib/optionsresolver/exceptions/invalid_options.rb
|
25
|
+
- lib/optionsresolver/exceptions/invalid_parameter.rb
|
26
|
+
- lib/optionsresolver/exceptions/missing_options.rb
|
27
|
+
- lib/optionsresolver/exceptions/undefined_options.rb
|
28
|
+
- lib/optionsresolver/optionsresolver.rb
|
29
|
+
- lib/optionsresolver/utils/hash_ext.rb
|
30
|
+
- lib/rb-optionsresolver.rb
|
31
|
+
- rb-optionsresolver.gemspec
|
32
|
+
homepage: https://github.com/namnv609/rb-optionsresolver
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 2.7.6
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Allows to create an options system with required options, defaults, validation
|
56
|
+
(type, value), normalization and more.
|
57
|
+
test_files: []
|