opt_struct 0.9.0 → 1.2.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 +4 -4
- data/README.md +291 -83
- data/lib/opt_struct.rb +6 -5
- data/lib/opt_struct/class_methods.rb +85 -42
- data/lib/opt_struct/instance_methods.rb +12 -13
- data/lib/opt_struct/module_methods.rb +2 -2
- data/lib/opt_struct/version.rb +1 -1
- data/opt_struct.gemspec +0 -4
- metadata +4 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 824012017028badde180165fa6280f398cbf138f09f8600319625e92fbaf67ed
|
|
4
|
+
data.tar.gz: 2c612d601c220eb88aaaf656917cd06d0edc7a87b70f84c67cda6b17e6cbbe94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7cf9e8c1d9e0b9c40a2f08c1f32784fa61b38a14edac39f4bef3f887845827a57f9b813e2f8df6dbb0436734c8f5895d7b25f45adb2bff3fa82eb7e87766fee
|
|
7
|
+
data.tar.gz: aea1084efba08f15951e7d8629c1a8211d66bddd720dfee19a1c1ec5a8eb52d6c73f047ac4ae8c5b7a969912113b0093249f48f991415ca932170d64e69b956d
|
data/README.md
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
# The Opt Struct
|
|
1
|
+
# The Opt Struct [![Build Status][travis-image]][travis-link]
|
|
2
|
+
|
|
3
|
+
[travis-image]: https://travis-ci.org/carlzulauf/opt_struct.svg?branch=master
|
|
4
|
+
[travis-link]: http://travis-ci.org/carlzulauf/opt_struct
|
|
2
5
|
|
|
3
6
|
A struct around a hash. Great for encapsulating actions with complex configuration, like interactor/action classes.
|
|
4
7
|
|
|
@@ -6,137 +9,342 @@ A struct around a hash. Great for encapsulating actions with complex configurati
|
|
|
6
9
|
gem "opt_struct"
|
|
7
10
|
```
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
# Examples
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
## Creating an OptStruct
|
|
12
15
|
|
|
13
16
|
```ruby
|
|
14
|
-
|
|
17
|
+
class User < OptStruct.new
|
|
18
|
+
required :email, :name
|
|
19
|
+
option :role, default: "member"
|
|
20
|
+
|
|
21
|
+
def formatted_email
|
|
22
|
+
%{"#{name}" <#{email}>}
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
# => argument error
|
|
27
|
+
## Using an OptStruct
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
```ruby
|
|
30
|
+
user = User.new(email: "admin@user.com", name: "Ms. Admin", role: "admin")
|
|
31
|
+
|
|
32
|
+
# option accessors are available
|
|
33
|
+
user.name
|
|
34
|
+
# => "Ms. Admin"
|
|
35
|
+
user.formatted_email
|
|
36
|
+
# => "\"Ms. Admin\" <admin@user.com>"
|
|
37
|
+
user.name = "Amber Admin"
|
|
38
|
+
# => "Amber Admin"
|
|
39
|
+
|
|
40
|
+
# values are also accessible through the `#options` Hash
|
|
41
|
+
user.options
|
|
42
|
+
# => {:email=>"admin@user.com", :name=>"Amber Admin", :role=>"admin"}
|
|
43
|
+
user.options.fetch(:role)
|
|
44
|
+
# => "admin"
|
|
45
|
+
```
|
|
21
46
|
|
|
22
|
-
|
|
23
|
-
# => #<MyClass>
|
|
47
|
+
# Documentation
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
i.fetch(:yin)
|
|
29
|
-
# => "yang"
|
|
30
|
-
```
|
|
49
|
+
## Use As Inheritable Class
|
|
50
|
+
|
|
51
|
+
`OptStruct.new` returns an instance of `Class` that can be inherited or initialized directly.
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
The following are functionally equivalent
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
```ruby
|
|
56
|
+
class User < OptStruct.new
|
|
57
|
+
required :email
|
|
58
|
+
option :name
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
User = OptStruct.new do
|
|
64
|
+
required :email
|
|
65
|
+
option :name
|
|
66
|
+
end
|
|
67
|
+
```
|
|
35
68
|
|
|
36
|
-
|
|
69
|
+
`OptStruct` classes can safely have descendants with their own isolated options.
|
|
37
70
|
|
|
38
71
|
```ruby
|
|
39
|
-
class
|
|
40
|
-
required :
|
|
41
|
-
option :bar, default: "foo"
|
|
72
|
+
class AdminUser < User
|
|
73
|
+
required :token
|
|
42
74
|
end
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
# =>
|
|
76
|
+
User.new(email: "regular@user.com")
|
|
77
|
+
# => #<User:0x0... @options={:email=>"regular@user.com"}>
|
|
78
|
+
|
|
79
|
+
AdminUser.new(email: "admin@user.com")
|
|
80
|
+
# ArgumentError: missing required keywords: [:token]
|
|
46
81
|
|
|
47
|
-
|
|
48
|
-
# => #<
|
|
49
|
-
i.foo
|
|
50
|
-
# => "bar"
|
|
51
|
-
i.bar
|
|
52
|
-
# => "foo"
|
|
53
|
-
i.foo = "foo"
|
|
54
|
-
i.options
|
|
55
|
-
# => {foo: "foo", bar: "foo", yin: "yang"}
|
|
82
|
+
AdminUser.new(email: "admin@user.com", token: "a2236843f0227af2")
|
|
83
|
+
# => #<AdminUser:0x0... @options={:email=>"admin@user.com", :token=>"..."}>
|
|
56
84
|
```
|
|
57
85
|
|
|
58
|
-
##
|
|
86
|
+
## Use As Mixin Module
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
`OptStruct.build` returns an instance of `Module` that can be included into a class or another module.
|
|
89
|
+
|
|
90
|
+
The following are functionally equivalent
|
|
61
91
|
|
|
62
92
|
```ruby
|
|
63
|
-
|
|
64
|
-
include OptStruct
|
|
65
|
-
|
|
93
|
+
module Visitable
|
|
94
|
+
include OptStruct.build
|
|
95
|
+
options :expected_at, :arrived_at, :departed_at
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class AuditLog
|
|
99
|
+
include Visitable
|
|
66
100
|
end
|
|
101
|
+
```
|
|
67
102
|
|
|
68
|
-
|
|
69
|
-
|
|
103
|
+
```ruby
|
|
104
|
+
Visitable = OptStruct.build { options :expected_at, :arrived_at, :departed_at }
|
|
70
105
|
|
|
71
|
-
|
|
72
|
-
|
|
106
|
+
class AuditLog
|
|
107
|
+
include Visitable
|
|
108
|
+
end
|
|
73
109
|
```
|
|
74
110
|
|
|
75
|
-
|
|
111
|
+
These examples result in an `AuditLog` class with identical behavior, but no explicit `Visitable` module.
|
|
76
112
|
|
|
77
|
-
|
|
113
|
+
```ruby
|
|
114
|
+
class AuditLog
|
|
115
|
+
include OptStruct.build
|
|
116
|
+
options :expected_at, :arrived_at, :departed_at
|
|
117
|
+
end
|
|
118
|
+
```
|
|
78
119
|
|
|
79
120
|
```ruby
|
|
80
|
-
class
|
|
81
|
-
include
|
|
121
|
+
class AuditLog
|
|
122
|
+
include(OptStruct.build do
|
|
123
|
+
options :expected_at, :arrived_at, :departed_at
|
|
124
|
+
end)
|
|
82
125
|
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Optional Arguments
|
|
129
|
+
|
|
130
|
+
Optional arguments are simply accessor methods for values expected to be in the `#options` Hash. Optional arguments can be defined in multiple ways.
|
|
83
131
|
|
|
84
|
-
|
|
85
|
-
# => argument error
|
|
132
|
+
All of the examples in this section are functionally equivalent.
|
|
86
133
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
```ruby
|
|
135
|
+
class User < OptStruct.new
|
|
136
|
+
option :email
|
|
137
|
+
option :role, default: "member"
|
|
138
|
+
end
|
|
90
139
|
```
|
|
91
140
|
|
|
92
|
-
|
|
141
|
+
```ruby
|
|
142
|
+
class User < OptStruct.new
|
|
143
|
+
options :email, role: "member"
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
class User < OptStruct.new
|
|
149
|
+
options email: nil, role: "member"
|
|
150
|
+
end
|
|
151
|
+
```
|
|
93
152
|
|
|
94
|
-
|
|
153
|
+
Passing a Hash to `.new` or `.build` is equivalent to passing the same hash to `options`
|
|
95
154
|
|
|
96
155
|
```ruby
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
option :last_name
|
|
156
|
+
User = OptStruct.new(email: nil, role: "member")
|
|
157
|
+
```
|
|
100
158
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
Default blocks can also be used and are late evaluated on the each struct instance.
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class User < OptStruct.new
|
|
163
|
+
option :email, default: -> { nil }
|
|
164
|
+
option :role, -> { "member" }
|
|
104
165
|
end
|
|
166
|
+
```
|
|
105
167
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
t.name
|
|
112
|
-
# => "Trish Smith"
|
|
168
|
+
```ruby
|
|
169
|
+
class User < OptStruct.new
|
|
170
|
+
options :email, role: -> { "member" }
|
|
171
|
+
end
|
|
172
|
+
```
|
|
113
173
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
174
|
+
```ruby
|
|
175
|
+
class User < OptStruct.new
|
|
176
|
+
option :email, nil
|
|
177
|
+
option :role, -> { default_role }
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def default_role
|
|
182
|
+
"member"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
117
186
|
|
|
118
|
-
|
|
119
|
-
|
|
187
|
+
Default symbols are treated as method calls if the struct `#respond_to?` the method.
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
class User < OptStruct.new
|
|
191
|
+
options :email, :role => :default_role
|
|
192
|
+
|
|
193
|
+
def default_role
|
|
194
|
+
"member"
|
|
120
195
|
end
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Required Arguments
|
|
200
|
+
|
|
201
|
+
Required arguments are just like optional arguments, except they are also added to the `.required_keys` collection, which is checked when an OptStruct is initialized. If the `#options` Hash does not contain all `.required_keys` then an `ArgumentError` is raised.
|
|
202
|
+
|
|
203
|
+
The following examples are functionally equivalent.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class Student < OptStruct.new
|
|
207
|
+
required :name
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class Student < OptStruct.new
|
|
213
|
+
option :name, required: true
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
class Student < OptStruct.new
|
|
219
|
+
option :name
|
|
220
|
+
required_keys << :name
|
|
221
|
+
end
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Expected Arguments
|
|
225
|
+
|
|
226
|
+
OptStructs can accept non-keyword arguments if the struct knows to expect them.
|
|
227
|
+
|
|
228
|
+
For code like this to work...
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
user = User.new("admin@user.com", "admin")
|
|
232
|
+
user.email # => "admin@user.com"
|
|
233
|
+
user.role # => "admin"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
... the OptStruct needs to have some `.expected_arguments`.
|
|
237
|
+
|
|
238
|
+
The following `User` class examples are functionally equivalent and allow the code above to function.
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
User = OptStruct.new(:email, :role)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
class User < OptStruct.new(:email)
|
|
246
|
+
expect_argument :role
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
class User
|
|
252
|
+
include OptStruct.build(:email, :role)
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
class User
|
|
258
|
+
include OptStruct.build
|
|
259
|
+
expect_arguments :email, :role
|
|
260
|
+
end
|
|
261
|
+
```
|
|
121
262
|
|
|
263
|
+
```ruby
|
|
264
|
+
class User < OptStruct.new(:email)
|
|
265
|
+
expected_arguments << :role
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Expected arguments are similar to required arguments, except they are in `.expected_arguments` collection, which is checked when an OptStruct is initialized.
|
|
270
|
+
|
|
271
|
+
Expected arguments can also be supplied using keywords. An `ArgumentError` is only raised if the expected argument is not in the list of arguments passed to `OptStruct#new` **and** the argument is not present in the `#options` Hash.
|
|
272
|
+
|
|
273
|
+
The following examples will initialize any of the `User` class examples above without error.
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
User.new(email: "example@user.com", role: "member")
|
|
277
|
+
User.new("example@user.com", role: "member")
|
|
278
|
+
User.new(role: "member", email: "example@user.com")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## The `#options` Hash
|
|
282
|
+
|
|
283
|
+
All OptStruct arguments are read from and stored in a single `Hash` instance. This Hash can be accessed directly using the `options` method.
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
Person = OptStruct.new(:name)
|
|
287
|
+
Person.new(name: "John", age: 32).options
|
|
288
|
+
# => {:name=>"John", :age=>32}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Feel free to write your own accessor methods for things like dependent options or other complex/private behavior.
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
class Person < OptStruct.new
|
|
295
|
+
option :given_name
|
|
296
|
+
option :family_name
|
|
297
|
+
|
|
122
298
|
def name
|
|
123
|
-
|
|
299
|
+
options.fetch(:name) { "#{given_name} #{family_name}" }
|
|
124
300
|
end
|
|
125
301
|
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## On Initialization
|
|
126
305
|
|
|
127
|
-
|
|
128
|
-
|
|
306
|
+
All of the following examples are functionally equivalent.
|
|
307
|
+
|
|
308
|
+
OptStruct classes are initialized in an `initialize` method (in `OptStruct::InstanceMethods`) like most classes. Also, like most classes, you can override `initialize` as long as you remember to call `super` properly to retain `OptStruct` functionality.
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
class UserReportBuilder < OptStruct.new(:user)
|
|
312
|
+
attr_reader :report
|
|
313
|
+
|
|
314
|
+
def initialize(*)
|
|
315
|
+
super
|
|
316
|
+
@report = []
|
|
317
|
+
end
|
|
129
318
|
end
|
|
319
|
+
```
|
|
130
320
|
|
|
131
|
-
|
|
132
|
-
c.name
|
|
133
|
-
# => "2012 Infinit G37"
|
|
321
|
+
`OptStruct` also provides initialization callbacks to make hooking into and customizing the initialization of OptStruct classes easier and require less code.
|
|
134
322
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
323
|
+
```ruby
|
|
324
|
+
class UserReportBuilder < OptStruct.new(:user)
|
|
325
|
+
attr_reader :report
|
|
326
|
+
init { @report = [] }
|
|
327
|
+
end
|
|
328
|
+
```
|
|
138
329
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
330
|
+
```ruby
|
|
331
|
+
class UserReportBuilder < OptStruct.new(:user)
|
|
332
|
+
attr_reader :report
|
|
333
|
+
|
|
334
|
+
around_init do |instance|
|
|
335
|
+
instance.call
|
|
336
|
+
@report = []
|
|
337
|
+
end
|
|
338
|
+
end
|
|
142
339
|
```
|
|
340
|
+
|
|
341
|
+
Available callbacks
|
|
342
|
+
|
|
343
|
+
* `around_init`
|
|
344
|
+
* `before_init`
|
|
345
|
+
* `init`
|
|
346
|
+
* `after_init`
|
|
347
|
+
|
|
348
|
+
## Inheritance, Expanded
|
|
349
|
+
|
|
350
|
+
See `spec/inheritance_spec.rb` for examples of just how crazy you can get.
|
data/lib/opt_struct.rb
CHANGED
|
@@ -3,10 +3,11 @@ require "opt_struct/module_methods"
|
|
|
3
3
|
require "opt_struct/instance_methods"
|
|
4
4
|
|
|
5
5
|
module OptStruct
|
|
6
|
+
RESERVED_WORDS = %i(class defaults options fetch check_required_args check_required_keys).freeze
|
|
6
7
|
|
|
7
|
-
def self._inject_struct(target, source, args = [], defaults
|
|
8
|
+
def self._inject_struct(target, source, args = [], **defaults, &callback)
|
|
8
9
|
structs = Array(source.instance_variable_get(:@_opt_structs)).dup
|
|
9
|
-
if args.any? || defaults.any? ||
|
|
10
|
+
if args.any? || defaults.any? || block_given?
|
|
10
11
|
structs << [args, defaults, callback]
|
|
11
12
|
end
|
|
12
13
|
target.instance_variable_set(:@_opt_structs, structs)
|
|
@@ -18,7 +19,7 @@ module OptStruct
|
|
|
18
19
|
end
|
|
19
20
|
structs.each do |s_args, s_defaults, s_callback|
|
|
20
21
|
target.expect_arguments *s_args if s_args.any?
|
|
21
|
-
target.options s_defaults
|
|
22
|
+
target.options **s_defaults if s_defaults.any?
|
|
22
23
|
target.class_exec(&s_callback) if s_callback
|
|
23
24
|
end
|
|
24
25
|
else
|
|
@@ -38,10 +39,10 @@ module OptStruct
|
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def self.new(*args, **defaults, &callback)
|
|
41
|
-
_inject_struct(Class.new, self, args.map(&:to_sym), defaults, &callback)
|
|
42
|
+
_inject_struct(Class.new, self, args.map(&:to_sym), **defaults, &callback)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def self.build(*args, **defaults, &callback)
|
|
45
|
-
_inject_struct(Module.new, self, args.map(&:to_sym), defaults, &callback)
|
|
46
|
+
_inject_struct(Module.new, self, args.map(&:to_sym), **defaults, &callback)
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -1,75 +1,81 @@
|
|
|
1
1
|
module OptStruct
|
|
2
2
|
module ClassMethods
|
|
3
3
|
def inherited(subclass)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
subclass.send(:instance_variable_set, v, ivar.dup) if ivar
|
|
4
|
+
opt_struct_class_constants.each do |c|
|
|
5
|
+
subclass.const_set(c, const_get(c)) if const_defined?(c)
|
|
7
6
|
end
|
|
8
7
|
end
|
|
9
8
|
|
|
9
|
+
# overwritten if `required` is called
|
|
10
10
|
def required_keys
|
|
11
|
-
|
|
11
|
+
[].freeze
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def required(*keys)
|
|
15
|
-
|
|
16
|
-
option_accessor *keys
|
|
14
|
+
def required(*keys, **options)
|
|
15
|
+
add_required_keys *keys
|
|
16
|
+
option_accessor *keys, **options
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def option_reader(*keys)
|
|
20
|
-
check_reserved_words(keys)
|
|
21
|
-
|
|
19
|
+
def option_reader(*keys, **options)
|
|
22
20
|
keys.each do |key|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
options[key]
|
|
26
|
-
elsif defaults.key?(key)
|
|
27
|
-
options[key] = read_default_value(key)
|
|
21
|
+
class_eval <<~RUBY
|
|
22
|
+
#{options[:private] ? "private" : ""} def #{key}
|
|
23
|
+
options[:#{key}]
|
|
28
24
|
end
|
|
29
|
-
|
|
25
|
+
RUBY
|
|
30
26
|
end
|
|
31
27
|
end
|
|
32
28
|
|
|
33
|
-
def option_writer(*keys)
|
|
34
|
-
check_reserved_words(keys)
|
|
35
|
-
|
|
29
|
+
def option_writer(*keys, **options)
|
|
36
30
|
keys.each do |key|
|
|
37
|
-
|
|
31
|
+
class_eval <<~RUBY
|
|
32
|
+
#{options[:private] ? "private" : ""} def #{key}=(value)
|
|
33
|
+
options[:#{key}] = value
|
|
34
|
+
end
|
|
35
|
+
RUBY
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
|
|
41
|
-
def option_accessor(*keys)
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
def option_accessor(*keys, **options)
|
|
40
|
+
check_reserved_words(keys)
|
|
41
|
+
option_reader *keys, **options
|
|
42
|
+
option_writer *keys, **options
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
def option(key, default = nil, **options)
|
|
45
|
+
def option(key, default = nil, required: false, **options)
|
|
47
46
|
default = options[:default] if options.key?(:default)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
option_accessor key
|
|
47
|
+
add_defaults key => default
|
|
48
|
+
add_required_keys key if required
|
|
49
|
+
option_accessor key, **options
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
def options(*keys, **keys_defaults)
|
|
54
53
|
option_accessor *keys if keys.any?
|
|
55
54
|
if keys_defaults.any?
|
|
56
|
-
|
|
55
|
+
add_defaults keys_defaults
|
|
57
56
|
option_accessor *(keys_defaults.keys - expected_arguments)
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
def defaults
|
|
62
|
-
|
|
61
|
+
const_defined?(:OPT_DEFAULTS) ? const_get(:OPT_DEFAULTS) : {}
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
# overwritten if `expect_arguments` is called
|
|
65
|
+
def expected_arguments
|
|
66
|
+
[].freeze
|
|
68
67
|
end
|
|
69
68
|
|
|
70
|
-
def
|
|
71
|
-
|
|
69
|
+
def expect_arguments(*arguments)
|
|
70
|
+
required(*arguments)
|
|
71
|
+
combined = expected_arguments + arguments
|
|
72
|
+
class_eval <<~EVAL
|
|
73
|
+
def self.expected_arguments
|
|
74
|
+
#{combined.inspect}.freeze
|
|
75
|
+
end
|
|
76
|
+
EVAL
|
|
72
77
|
end
|
|
78
|
+
alias_method :expect_argument, :expect_arguments
|
|
73
79
|
|
|
74
80
|
def init(meth = nil, &blk)
|
|
75
81
|
add_callback(:init, meth || blk)
|
|
@@ -84,23 +90,60 @@ module OptStruct
|
|
|
84
90
|
add_callback(:around_init, meth || blk)
|
|
85
91
|
end
|
|
86
92
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
@_callbacks[name] ||= []
|
|
90
|
-
@_callbacks[name] << callback
|
|
93
|
+
def all_callbacks
|
|
94
|
+
const_defined?(:OPT_CALLBACKS) ? const_get(:OPT_CALLBACKS) : {}.freeze
|
|
91
95
|
end
|
|
92
96
|
|
|
93
|
-
def
|
|
94
|
-
|
|
97
|
+
def shareable?
|
|
98
|
+
const_defined?(:SHAREABLE) && const_get(:SHAREABLE)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def shareable!
|
|
102
|
+
return if shareable?
|
|
103
|
+
const_set(:SHAREABLE, true)
|
|
95
104
|
end
|
|
96
105
|
|
|
97
106
|
private
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
def share(value)
|
|
109
|
+
return value unless shareable?
|
|
110
|
+
defined?(Ractor) ? Ractor.make_shareable(value) : value
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def add_required_keys(*keys)
|
|
114
|
+
combined = required_keys + keys
|
|
115
|
+
class_eval <<~RUBY
|
|
116
|
+
def self.required_keys
|
|
117
|
+
#{combined.inspect}.freeze
|
|
118
|
+
end
|
|
119
|
+
RUBY
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_defaults(defaults_to_add)
|
|
123
|
+
freezer = defaults.dup
|
|
124
|
+
defaults_to_add.each { |k, v| freezer[k] = share(v) }
|
|
125
|
+
remove_const(:OPT_DEFAULTS) if const_defined?(:OPT_DEFAULTS)
|
|
126
|
+
const_set(:OPT_DEFAULTS, freezer.freeze)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def add_callback(name, callback)
|
|
130
|
+
if const_defined?(:OPT_CALLBACKS)
|
|
131
|
+
callbacks_for_name = (all_callbacks[name] || []) + [callback]
|
|
132
|
+
callbacks_hash = all_callbacks.merge(name => callbacks_for_name).freeze
|
|
133
|
+
remove_const(:OPT_CALLBACKS)
|
|
134
|
+
const_set(:OPT_CALLBACKS, callbacks_hash)
|
|
135
|
+
else
|
|
136
|
+
const_set(:OPT_CALLBACKS, { name => [ callback ] })
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def opt_struct_class_constants
|
|
141
|
+
[:OPT_DEFAULTS, :OPT_CALLBACKS]
|
|
142
|
+
end
|
|
100
143
|
|
|
101
144
|
def check_reserved_words(words)
|
|
102
145
|
Array(words).each do |word|
|
|
103
|
-
if RESERVED_WORDS.member?(word)
|
|
146
|
+
if OptStruct::RESERVED_WORDS.member?(word)
|
|
104
147
|
raise ArgumentError, "Use of reserved word is not permitted: #{word.inspect}"
|
|
105
148
|
end
|
|
106
149
|
end
|
|
@@ -4,6 +4,7 @@ module OptStruct
|
|
|
4
4
|
with_init_callbacks do
|
|
5
5
|
@options = options
|
|
6
6
|
assign_arguments(arguments)
|
|
7
|
+
assign_defaults
|
|
7
8
|
check_required_keys
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -24,23 +25,21 @@ module OptStruct
|
|
|
24
25
|
raise ArgumentError, "missing required keywords: #{missing.inspect}"
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
def assign_defaults
|
|
30
|
+
defaults.each do |key, default_value|
|
|
31
|
+
next if options.key?(key) # || default_value.nil?
|
|
32
|
+
options[key] = read_default_value(default_value)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
def assign_arguments(args)
|
|
29
37
|
self.class.expected_arguments.map.with_index do |key, i|
|
|
30
|
-
if args.length > i
|
|
31
|
-
options[key] = args[i]
|
|
32
|
-
elsif !options.key?(key)
|
|
33
|
-
if defaults.key?(key)
|
|
34
|
-
options[key] = read_default_value(key)
|
|
35
|
-
else
|
|
36
|
-
raise ArgumentError, "missing required argument: #{key}"
|
|
37
|
-
end
|
|
38
|
-
end
|
|
38
|
+
options[key] = args[i] if args.length > i
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
|
-
|
|
42
|
-
def read_default_value(
|
|
43
|
-
default = defaults[key]
|
|
41
|
+
|
|
42
|
+
def read_default_value(default)
|
|
44
43
|
case default
|
|
45
44
|
when Proc
|
|
46
45
|
instance_exec(&default)
|
|
@@ -22,8 +22,8 @@ module OptStruct
|
|
|
22
22
|
options
|
|
23
23
|
expect_arguments
|
|
24
24
|
).each do |class_method|
|
|
25
|
-
define_method(class_method) do |*args|
|
|
26
|
-
@_opt_structs << [[], {}, -> { send(class_method, *args) }]
|
|
25
|
+
define_method(class_method) do |*args, **options|
|
|
26
|
+
@_opt_structs << [[], {}, -> { send(class_method, *args, **options) }]
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
data/lib/opt_struct/version.rb
CHANGED
data/opt_struct.gemspec
CHANGED
|
@@ -16,8 +16,4 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.files = `git ls-files`.split("\n").grep(/^lib/)
|
|
17
17
|
spec.files += %w(README.md opt_struct.gemspec)
|
|
18
18
|
spec.require_paths = ["lib"]
|
|
19
|
-
|
|
20
|
-
spec.add_development_dependency "bundler", "~> 1.14"
|
|
21
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
|
22
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
|
23
19
|
end
|
metadata
CHANGED
|
@@ -1,57 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opt_struct
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carl Zulauf
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.14'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.14'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rake
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '10.0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '10.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rspec
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '3.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '3.0'
|
|
11
|
+
date: 2021-03-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
55
13
|
description: Struct with support for keyword params and mixin support
|
|
56
14
|
email:
|
|
57
15
|
- carl@linkleaf.com
|
|
@@ -85,7 +43,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
85
43
|
- !ruby/object:Gem::Version
|
|
86
44
|
version: '0'
|
|
87
45
|
requirements: []
|
|
88
|
-
rubygems_version: 3.
|
|
46
|
+
rubygems_version: 3.1.4
|
|
89
47
|
signing_key:
|
|
90
48
|
specification_version: 4
|
|
91
49
|
summary: The Option Struct
|