ns-options 0.4.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +2 -1
  3. data/LICENSE +1 -1
  4. data/README.md +187 -301
  5. data/Rakefile +1 -1
  6. data/lib/ns-options/assert_macros.rb +9 -12
  7. data/lib/ns-options/boolean.rb +2 -0
  8. data/lib/ns-options/namespace.rb +34 -134
  9. data/lib/ns-options/namespace_advisor.rb +35 -0
  10. data/lib/ns-options/namespace_data.rb +166 -0
  11. data/lib/ns-options/namespaces.rb +23 -12
  12. data/lib/ns-options/option.rb +50 -24
  13. data/lib/ns-options/options.rb +23 -49
  14. data/lib/ns-options/proxy.rb +40 -53
  15. data/lib/ns-options/proxy_method.rb +54 -0
  16. data/lib/ns-options/root_methods.rb +77 -0
  17. data/lib/ns-options/version.rb +1 -1
  18. data/lib/ns-options.rb +18 -8
  19. data/ns-options.gemspec +3 -4
  20. data/test/helper.rb +3 -10
  21. data/test/support/app.rb +3 -1
  22. data/test/support/proxy.rb +4 -0
  23. data/test/support/type_class_proxy.rb +29 -0
  24. data/test/support/user.rb +5 -5
  25. data/test/{integration/app_test.rb → system/app_tests.rb} +8 -6
  26. data/test/{integration/proxy_test.rb → system/proxy_tests.rb} +12 -0
  27. data/test/system/type_class_proxy_tests.rb +108 -0
  28. data/test/system/user_tests.rb +146 -0
  29. data/test/unit/{ns-options/boolean_test.rb → boolean_tests.rb} +5 -4
  30. data/test/unit/namespace_advisor_tests.rb +69 -0
  31. data/test/unit/namespace_data_tests.rb +336 -0
  32. data/test/unit/namespace_tests.rb +205 -0
  33. data/test/unit/namespaces_tests.rb +99 -0
  34. data/test/unit/{ns-options/option_test.rb → option_tests.rb} +155 -93
  35. data/test/unit/options_tests.rb +152 -0
  36. data/test/unit/proxy_method_tests.rb +87 -0
  37. data/test/unit/{ns-options/proxy_test.rb → proxy_tests.rb} +52 -0
  38. data/test/unit/root_methods_tests.rb +126 -0
  39. metadata +58 -63
  40. data/lib/ns-options/errors/invalid_name.rb +0 -15
  41. data/lib/ns-options/has_options.rb +0 -53
  42. data/lib/ns-options/helper/advisor.rb +0 -88
  43. data/lib/ns-options/helper.rb +0 -87
  44. data/test/integration/user_test.rb +0 -94
  45. data/test/unit/ns-options/has_options_test.rb +0 -90
  46. data/test/unit/ns-options/helper/advisor_test.rb +0 -148
  47. data/test/unit/ns-options/helper_test.rb +0 -56
  48. data/test/unit/ns-options/namespace_test.rb +0 -432
  49. data/test/unit/ns-options/namespaces_test.rb +0 -55
  50. data/test/unit/ns-options/options_test.rb +0 -221
  51. /data/test/unit/{ns-options/assert_macros_test.rb → assert_macros_tests.rb} +0 -0
data/README.md CHANGED
@@ -1,102 +1,61 @@
1
- # Namespace Options
1
+ # NsOptions
2
2
 
3
- Namespace Options provides a clean interface for defining, organizing and using options. Options are defined through a clean syntax that clearly shows what can be set and read. Options can be organized into namespaces as well. This allows multiple libraries to share common options but still have their own specific options separated. Reading and writing options is as simple as if they were accessors. Furthermore, you can provide your own _type_ class that allows you to provide extended functionality to a simple option.
4
-
5
- ## Installation
6
-
7
- Add the following to your Gemfile and `bundle install`:
8
-
9
- gem 'ns-options'
3
+ A Ruby DSL for defining, organizing and accessing options. Use namespaces to organize options. Read and write option values using accessors.
10
4
 
11
5
  ## Usage
12
6
 
13
- ### Defining Options
14
-
15
- The basic usage of Namespace Options is to be able to define options for a module or class:
16
-
17
7
  ```ruby
8
+ require 'ns-options'
9
+
18
10
  module App
19
11
  include NsOptions
12
+
20
13
  options(:settings) do
21
14
  option :root, Pathname
22
15
  option :stage
23
16
  end
24
17
  end
25
- ```
26
-
27
- The code above makes the `App` module now have options, accessible through the settings method. The options can be read and written to like a normal accessor:
28
18
 
29
- ```ruby
30
19
  App.settings.root = "/a/path/to/the/root"
31
- App.settings.root.join("log", "test.log") # => "/a/path/to/the/root/log/test.log" (a Pathname instance)
32
-
33
- App.settings.stage = "development"
34
- App.settings.stage # => "development"
35
- ```
36
-
37
- Because the `root` option specified `Pathname` as it's type, the option will always return an instance of `Pathname`. Since the `stage` option did not specify a type, it defaulted to an `Object` which allows it to accept any value. You can define you're own type classes as well and use them:
38
-
39
- ```ruby
40
- class Stage < String
41
-
42
- def initialize(value)
43
- super(value.to_s)
44
- end
45
-
46
- def development?
47
- self == "development"
48
- end
49
-
50
- end
51
-
52
-
53
- App.settings.define do
54
- option :stage, Stage
55
- end
20
+ App.settings.root.join("log", "test.log") #=> "/a/path/to/the/root/log/test.log" (a Pathname instance)
56
21
 
57
22
  App.settings.stage = "development"
58
- App.settings.stage.development? # => true
59
- App.settings.stage = "test"
60
- App.settings.stage.development? # => false
23
+ App.settings.stage #=> "development"
61
24
  ```
62
25
 
63
- This allows you to add extended functionality to your options and is where a lot of nice usability can be added. Defining your own type classes is explained in more detail later.
26
+ The code above defines a `settings` reader on `App`. The options can be read and written to using their accessors
64
27
 
65
28
  ### Namespaces
66
29
 
67
- Namespaces allow you to organize your options. With the previously mentioned `App` module and it's options you could create a namespace for another library:
68
-
69
30
  ```ruby
70
- module Data # data is a library for retrieving persisted data from some resource
71
-
72
- App.settings.namespace(:data) do
73
- option :server
74
- end
31
+ options(:settings) do
75
32
 
76
- def self.config
77
- App.settings.data
33
+ namespace :grouped_stuff do
34
+ option :something
35
+ option :something_else
78
36
  end
79
37
 
80
38
  end
81
39
  ```
82
40
 
83
- Now I can set a server option for data that is separate from the main `App` settings:
41
+ Namespaces allow you to organize your options. You access the namespace and its options through their accessors.
84
42
 
85
43
  ```ruby
86
- Data.config.server = "127.0.0.1:1234"
87
-
88
- App.settings.server # => NoMethodError
89
- App.settings.data.server # => 127.0.0.1:1234
44
+ App.settings.grouped_stuff.something = 1
45
+ App.settings.grouped_stuff.something # => 1
90
46
  ```
91
47
 
92
48
  ### Less Verbose Definitions
93
49
 
94
- As an alternative to the above definition syntax, you can use an alternate less-verbose syntax:
50
+ As an alternative to the above definition syntax, you can use a less-verbose syntax:
51
+
95
52
  * `opts` for `options`
96
53
  * `opt` for `option`
97
54
  * `ns` for `namespace`
98
55
 
99
56
  ```ruby
57
+ require 'ns-options'
58
+
100
59
  module App
101
60
  include NsOptions
102
61
 
@@ -108,343 +67,266 @@ module App
108
67
  opt :something
109
68
  end
110
69
  end
70
+
111
71
  end
112
72
  ```
113
73
 
114
- #### With Classes
74
+ ### Dynamically Defined Options
115
75
 
116
- Using `NsOptions` on a `Class` uses namespaces to create separate sets of options for every instance of your class created. This allows every instance to have different values for the set of options and not interfere with each other. For example with the following:
76
+ Not all options have to be defined formally ahead of time. You can write any option value you like at any time.
117
77
 
118
78
  ```ruby
119
- class User
120
- include NsOptions
121
- options(:preferences) do
122
- option :home_page
123
- end
124
-
125
- end
79
+ App.settings.a_value #=> NoMethodError: undefined method `a_value'...
80
+ App.settings.a_value = 1
81
+ App.settings.a_value #=> 1
126
82
  ```
127
83
 
128
- A namespace is created for the `User` class in the same way it works for modules:
84
+ ### Mass Assigning Options
85
+
86
+ Sometimes, it's convenient to be able to set many options at once. This can be done by calling the `apply` method and giving it a hash of option names with values. You can even give it keys that aren't pre-defined options - new options will be created for them
129
87
 
130
88
  ```ruby
131
- User.preferences # => NsOptions::Namespace instance
132
- User.preferences.home_page = "/home" # you can set options at this level, though I'm not sure why you would
89
+ App.settings.apply({
90
+ :root => "/path/to/project",
91
+ :stage => "development"
92
+ :new_value => 1
93
+ })
94
+
95
+ App.settings.root #=> "/path/to/project"
96
+ App.settings.stage #=> "development"
97
+ App.settings.new_value #=> 1
133
98
  ```
134
99
 
135
- Additionally, `NsOptions` will setup instances of a class to have a _copy_ of their class's namespace.
100
+ To get a hash of values for a namespace, just call its `to_hash` method.
136
101
 
137
- ```ruby
138
- user = User.new
139
- user.preferences.home_page = "/home" # makes a lot more sense to do this
140
- user2 = User.new
141
- user2.preferences.home_page = "/not_home"
142
- user.preferences.home_page == user2.preferences.home_page # => false, they are completely separate
143
- ```
102
+ ## Class Behavior
144
103
 
145
- The instance level namespaces are deep copies of the class one. This means every option and sub-namespaces will be included. Only values are not copied.
104
+ Using `NsOptions` on a `Class` uses namespaces to create separate sets of options for every instance of your class. This different instances to have different options values but share the same definition.
105
+
106
+ To illustrate:
146
107
 
147
108
  ```ruby
148
109
  class User
149
110
  include NsOptions
150
111
  options(:preferences) do
151
112
  option :home_page
152
- namespace :view do
153
- option :background_color, ViewColor
154
- end
155
113
  end
156
114
 
157
115
  end
158
116
 
159
- User.preferences.home_page = "/home"
160
- user = User.new
161
- user.preferences.home_page # => nil, does not return '/home'
162
- user.preferences.object_id != User.preferences.object_id # => true, they are different objects, just with the same definition
163
- user.preferences.view.background_color = "green"
164
- user.preferences.view.background_color # => returns an instance of ViewColor
117
+ User.preferences # => NsOptions::Namespace instance
165
118
  ```
166
119
 
167
- ### Dynamically Defined Options
168
-
169
- Not all options have to be defined formally. Though defining an option through the `option` method allows the most functionality and allows for quickly seeing what can be used, Namespace Options allows you to write options that have not been defined:
120
+ A `preferences` namespace is created for the `User` class. For each instiance of `User` created, `NsOptions` will setup an identical _copy_ of their class's namespace. However, each instance sets and maintains unique option values.
170
121
 
171
122
  ```ruby
172
- App.settings.logger = Logger.new(App.settings.root.join("log", "test.log"))
123
+ user1 = User.new
124
+ user1.preferences.home_page = "/home"
125
+
126
+ user2 = User.new
127
+ user2.preferences.home_page = "/not_home"
173
128
 
174
- App.settings.logger.info("Hello World")
129
+ user.preferences.home_page == user2.preferences.home_page #=> false
175
130
  ```
176
131
 
177
- Writing to a namespace with a previously undefined option will create a new option. The type class will be defaulted to `Object` as if you didn't provide it. This will allow you to set any value for the option so you have no guarantee on what it's value is and how it can be used.
132
+ ## Options
178
133
 
179
- ### Mass Assigning Options
134
+ ### Type Classes
180
135
 
181
- Sometimes, it's convenient to be able to set many options at once. This can be done by calling the `apply` method and giving it a hash of option names with values:
136
+ Options can be defined with a given "type class". If none is specified, `Object` is used.
182
137
 
183
138
  ```ruby
184
- class Project
185
- include NsOptions
186
- options(:settings) do
187
- option :file_path
188
- option :home_page
189
-
190
- namespace(:movie_resolution) do
191
- option :height, Integer
192
- option :width, Integer
193
- end
194
- end
139
+ options :settings do
140
+ option :opt1
141
+ option :opt2, MyCustomTypeClass
195
142
  end
196
-
197
- project = Project.new
198
- project.settings.apply({
199
- :file_path => "/path/to/project",
200
- :movie_resolution => { :height => 800, :width => 600 }
201
- })
202
-
203
- project.settings.file_path # => "/path/to/project"
204
- project.settings.movie_resolution.height # => 800
205
- project.settings.movie_resolution.width # => 600
206
143
  ```
207
144
 
208
- As the example shows, if you have a namespace and have a matching hash, it will automatically apply those values to that namespace. Also, if you include keys that are not defined options for your namespace, new options will be created for the values:
145
+ Understanding what NsOptions will do with your type class is important. First, option values will be cast to your type class. If you write a value that is not of a matching type, NsOptions will try to _coerce_ the value.
209
146
 
210
147
  ```ruby
211
- project = Project.new
212
- project.settings.apply({ :stereoscopic => true, :not_a_namespace => { :yes => true } })
148
+ # no type coercion is done here, the value is of the right type
149
+ settings.opt2 = MyCustomTypeClass.new(123)
213
150
 
214
- project.settings.stereoscopic # => true
215
- project.settings.not_a_namespace # => { :yes => true }
216
- ```
151
+ class BetterCustomTypeClass < MyCustomTypeClass; end
217
152
 
218
- The reverse is also supported, so if you want a `Hash` version of your namespace, just ask your options for it.
153
+ # again, no type coercion is done, as BetterCustomTypeClass is a kind of MyCustomTypeClass
154
+ settings.opt2 = BetterCustomTypeClass.new(456)
219
155
 
220
- ```ruby
221
- # a continuation of the previous block of code...
222
- project.settings.to_hash # => { :stereoscopic => true, :not_a_namespace => { :yes => true } }
223
- project.settings.each do |name, value|
224
- # iterating over your options works as well
225
- end
156
+ # here, type coercion is performed
157
+ # this is the equivalent of doing: `settings.opt2 = MyCustomTypeClass.new(789)`
158
+ settings.opt2 = 789
159
+
160
+ # nil is never coerced, if you set a value to nil, it's just nil
161
+ App.setting.stage = nil
226
162
  ```
227
163
 
228
- ### Lazily eval'd options
164
+ For type coercion to work, your type class's initializer must work given only a single argument.
229
165
 
230
- Sometimes, you may want to set an option to a value that shouldn't (couldn't) be evaluated until the option is read. If you set an option equal to a Proc, the value of the option will be whatever the return value of the Proc is at the time the option is read. Here are some examples:
166
+ ### Default Type Classes
231
167
 
232
168
  ```ruby
233
- # dynamic value
234
- options(:dynamic) do
235
- option :rand, :default => Proc.new { rand(1000) }
236
- end
169
+ class MyCustomFixNum < Struct.new(:num); end
237
170
 
238
- dynamic.rand #=> 347
239
- dynamic.rand #=> 529
240
-
241
- # same goes for dynamically defined options
242
- dynamic.not_originally_defined = Proc.new { rand(1000) }
243
- dynamic.not_originally_defined #=> 110
244
- dynamic.not_originally_defined #=> 931
245
- ```
171
+ options :settings do
172
+ option_type_class Fixnum
246
173
 
247
- ```ruby
248
- # self referential value
249
- options(:selfref) do
250
- option :something, :default => "123"
251
- option :else, :default => Proc.new { self.something }
174
+ option :opt1, :default => 1
175
+ option :opt2, :default => 2
176
+ option :opt3, MyCustomFixNum, :default => 3
252
177
  end
253
178
 
254
- selfref.something #=> "123"
255
- selfref.else #=> "123"
179
+ settings.opt1.class #=> Fixnum
180
+ settings.opt3.class #=> Fixnum
181
+ settings.opt3.class #=> MyCustomFixNum
256
182
  ```
257
183
 
258
- If you really want your option to read and write Procs and not do this lazy eval behavior, just define the option as a Proc option
184
+ By default, NsOptions will use `Object` for an option's type class if none is specified. You can override this on a per-namespaces basis using the `option_type_class` method. Call this and all options will be defined using the given class by default.
185
+
186
+ Note, this setting applies recursively, so all child namespaces honor it as well. You can override this by specifying a new type class on your child namespaces.
259
187
 
260
188
  ```ruby
261
- options(:explicit) do
262
- option :a_proc, Proc, :default => Proc.new { rand(1000) }
263
- end
189
+ # you can use an abbreviated syntax
190
+ #...
191
+ options :settings do
192
+ opt_type_class Fixnum
264
193
 
265
- explicit.a_proc #=> <the proc obj>
194
+ option :opt1, :default => 1
195
+ #...
196
+
197
+ # you can also pass in the option type class when defining the ns
198
+ #...
199
+ options :settings, Fixnum do
200
+ option :opt1, :default => 1
201
+ #...
266
202
  ```
267
203
 
268
- ### Custom Type Classes
204
+ ### Ruby Classes As A Type Class
269
205
 
270
- As stated previously, type classes is where you can add a lot of functionality and usability to your options. To do this though, understanding what `NsOptions` will do with your type class is important. First, it's important to understand when `NsOptions` will try to _coerce_ a value. This is only done when a value is not a _kind of_ the option's type class or when the value is nil. For example:
206
+ NsOptions will allow you to use many of Ruby's standard objects as type classes and still handle coercing values appropriately.
271
207
 
272
208
  ```ruby
273
- module App
209
+ module Example
274
210
  include NsOptions
275
- options :settings do
276
- option :stage, Stage
277
- end
278
- end
279
-
280
- App.settings.stage = Stage.new("development") # no type coercion is done here, the value is already a Stage
281
211
 
282
- class BetterStage < Stage
283
- # do something better
212
+ options :stuff do
213
+ option :string, String
214
+ option :integer, Integer
215
+ option :float, Float
216
+ option :symbol, Symbol
217
+ option :hash, Hash
218
+ option :array, Array
219
+ end
284
220
  end
285
221
 
286
- App.settings.stage = BetterStage.new("test") # again, no type coercion is done, as BetterStage is a kind of Stage
222
+ Example.stuff.string = 1
223
+ Example.stuff.string #=> "1", the same as doing String(1)
224
+ Example.stuff.integer = 5.0
225
+ Example.stuff.integer # => 5, this time it's Integer(5.0)
226
+ Example.stuff.float = "5.0"
227
+ Example.stuff.float #=> 5.0, same as Float("5.0")
287
228
 
288
- App.setting.stage = nil # nil is never coerced, if you set a value to nil, it's just nil
229
+ Example.stuff.symbol = "awesome"
230
+ Example.stuff.symbol #=> :awesome
231
+ Example.stuff.hash = { :a => 'b' }
232
+ Example.stuff.hash # => returns the same hash
233
+ Example.stuff.array = [ 1, 2, 3 ]
234
+ Example.stuff.array # => returns the same array
289
235
  ```
290
236
 
291
- Next, when `NsOptions` chooses to coerce a value with your class, it will always create a new instance of your type class and pass the value as the first argument. Your `initialize` method needs to be defined to handle this:
237
+ ### Rules
292
238
 
293
- ```ruby
294
- class Root < Pathname
295
- def initialize(path, app_name)
296
- super("#{path}/#{app_name}")
297
- end
298
- end
299
- ```
239
+ An option can be defined with certain rules that extend the behavior of the option.
300
240
 
301
- `Root`'s `initialize` method will not work for type coercion. The `app_name` argument will not be provided and Ruby will get angry. To solve this, make the `app_name` not required:
241
+ #### Default
302
242
 
303
243
  ```ruby
304
- class Root < Pathname
305
- def initialize(path, app_name = nil)
306
- app_name ||= App.settings.name # this might be one way to solve this
307
- super("#{path}/#{app_name}")
308
- end
244
+ settings do
245
+ option :opt1, :default => "development"
309
246
  end
247
+ settings.opt1 #=> 'development'
248
+ settings.opt1 = 'production' #=> 'production'
310
249
  ```
311
250
 
312
- With the revised `initialize` method, `NsOptions` will have no problems coercing values for the type class. In some cases the above solution may not work for you, but don't worry. See the _Option Rules_ section for another way to solve this, specifically about the args rule. For an example of a custom type class, the included `NsOptions::Boolean` can be looked at. This is a special case, but it works as a type class with `NsOptions`.
313
-
314
- ### Custom type class return values
315
-
316
- It may be useful to use a custom type class as a silent value handler. You don't necessarily care that this option is some handler class - you just want flexible ways to set its value and get a meaningful return value when you read it.
317
-
318
- When reading option values, NsOptions will first check and see if the option value responds to the `returned_value` method. If it does, NsOptions will return that value instead of the instance of the type class. If not it will return the type class instance as normal.
251
+ A default value runs through the same logic as if you set the value manually, so it will be coerced if necessary.
319
252
 
320
- The included `NsOptions::Boolean` handler class does just this to ensure it always returns either `true` or `false`. Here is another example:
253
+ #### Required
321
254
 
322
255
  ```ruby
323
- class HostedAt
324
- # sanitized :hosted_at config
325
- # remove any trailing '/'
326
- # ensure single leading '/'
327
-
328
- def initialize(value)
329
- @hosted_at = value.sub(/\/+$/, '').sub(/^\/*/, '/')
330
- end
331
-
332
- def returned_value
333
- @hosted_at
334
- end
256
+ settings do
257
+ option :opt1, :required => true
335
258
  end
336
259
 
337
- class Thing
338
- include NsOptions
260
+ settings.required_set? #=> false
261
+ settings.root = "/path/to/somewhere"
262
+ settings.required_set? #=> true
263
+ ```
339
264
 
340
- options :is do
341
- option :hosted_at, HostedAt
342
- end
343
- end
265
+ To check if an option is set it will simply check if the value is not `nil`. If you are using a custom type class though, you can define an `is_set?` method and this will be used to check if an option is set.
344
266
 
345
- thing = Thing.new
346
- thing.is.hosted_at # => nil
347
- thing.is.hosted_at = "path/to/resource/"
348
- thing.is.hosted_at # => "/path/to/resource"
349
- ```
267
+ The built in `required_set?` method checks to see if all the options for the namespace that have been marked `:required => true` are set. It will recursively check any sub-namespaces.
350
268
 
351
- ### Ruby Classes As A Type Class
269
+ #### Args
352
270
 
353
- `NsOptions` will allow you to use many of Ruby's standard objects as type classes and still handle coercing values appropriately. Typically this is done with ruby's type casting:
271
+ Another rule that you can specify is args.
354
272
 
355
273
  ```ruby
356
- module Example
357
- include NsOptions
358
- options :stuff do
359
- option :string, String
360
- option :integer, Integer
361
- option :float, Float
362
- option :symbol, Symbol
363
- option :hash, Hash
364
- option :array, Array
365
- end
274
+ class MyCustomTypeClass
275
+ def initialize(value, arg1, arg2); end
366
276
  end
367
277
 
368
- Example.stuff.string = 1
369
- Example.stuff.string # => "1", the same as doing String(1)
370
- Example.stuff.integer = 5.0
371
- Example.stuff.integer # => 5, this time it's Integer(5.0)
372
- Example.stuff.float = "5.0"
373
- Example.stuff.float # => 5.0, same as Float("5.0")
374
- ```
375
-
376
- `Symbol`, `Hash` and `Array` work, but ruby doesn't provide built in type casting for these.
278
+ settings do
279
+ option :opt1, MyCustomTypeClass, :args => lambda{ ["arg 1's value", "arg 2's value"] }
280
+ end
377
281
 
378
- ```ruby
379
- Example.stuff.symbol = "awesome"
380
- Example.stuff.symbol # => :awesome, watch out, this will try calling to_sym on the passed value, so it can error
381
- Example.stuff.hash = { :a => 'b' }
382
- Example.stuff.hash # => returns the same hash, does Hash.new.merge(value)
383
- Example.stuff.array = [ 1, 2, 3 ]
384
- Example.stuff.array # => returns the same array, Array is the only one that works without anything special, Array.new(value)
282
+ # equivalent to: `settings.opt1 = MyCustomTypeClass.new("a value", "arg 1's value", "arg 2's value")
283
+ settings.opt1 = 'a value'
385
284
  ```
386
285
 
387
- ### Option Rules
286
+ This allows you to pass additional arguments when coercing option values. The first argument will always be the value to coerce. Any additional arguments will be appended on after the value when calling the initializer.
287
+
388
288
 
389
- An option can be defined with certain rules (through a hash) that will extend the behavior of the option.
289
+ ### Lazily eval'd options
390
290
 
391
- #### Default Value
291
+ Sometimes, you may want to set an option to a value that shouldn't be evaluated until the option is read. If you set an option equal to a `Proc`, the value of the option will be whatever the return value of the Proc is at the time the option is read.
392
292
 
393
- The first rule is setting a default value.
293
+ Here are some examples:
394
294
 
395
295
  ```ruby
396
- App.settings do
397
- option :stage, Stage, :default => "development"
296
+ # dynamic value
297
+ options(:dynamic) do
298
+ option :rand, :default => Proc.new { rand(1000) }
398
299
  end
399
- App.settings.stage # => instead of nil this will be 'development'
400
- ```
401
-
402
- A default value runs through the same logic as if you set the value manually, so it will be coerced if necessary.
403
300
 
404
- #### Required
301
+ dynamic.rand #=> 347
302
+ dynamic.rand #=> 529
405
303
 
406
- It's also possible to flag an option as _required_.
407
304
 
408
- ```ruby
409
- App.settings do
410
- option :root, :required => true
305
+ # self referential value
306
+ options(:selfref) do
307
+ option :something, :default => "123"
308
+ option :else, :default => Proc.new { self.something }
411
309
  end
412
310
 
413
- App.settings.required_set? # => false, asking if the required options are set
414
- App.settings.root = "/path/to/somewhere"
415
- App.settings.required_set? # => true
311
+ selfref.something #=> "123"
312
+ selfref.else #=> "123"
313
+ selfref.something = 456
314
+ selfref.else #=> 456
416
315
  ```
417
316
 
418
- To check if an option is set it will simply check if the value is not `nil`. If you are using a custom type class though, you can define an `is_set?` method and this will be used to check if an option is set.
419
-
420
- The built in `required_set?` method checks to see if all the options for the namespace that have been marked `:required => true` are set. It does not recursively check any child namespaces.
421
-
422
- #### Args
423
-
424
- Another rule that you can specify is args. This allows you to pass more arguments to a type class.
317
+ If you really want your option to read and write Procs and not do this lazy eval behavior, just define the option with a `Proc` type class.
425
318
 
426
319
  ```ruby
427
- class Root < Pathname
428
- def initialize(path, app_name = nil)
429
- app_name = app_name.respond_to?(:call) ? app_name.call : app_name
430
- super("#{path}/#{app_name}")
431
- end
432
- end
433
-
434
- App.settings do
435
- option :name
436
- option :root, Root, :args => lambda{ App.settings.name }
320
+ options(:explicit) do
321
+ option :a_proc, Proc, :default => Proc.new { rand(1000) }
437
322
  end
438
323
 
439
- App.settings.name = "example"
440
- App.settings.root = "/path/to"
441
- App.settings.root # => /path/to/example, uses the args rule to build the path
324
+ explicit.a_proc #=> <the proc obj>
442
325
  ```
443
326
 
444
- With the args rule, you can have a type class accept more than one argument. The first argument will always be the value to coerce. Any more arguments will be appended on after the value.
445
-
446
327
  ## NsOptions::Proxy
447
- Mix in NsOptions::Proxy to any module/class to make it proxy a namespace. This essentially turns your class into a namespace itself. You can interact with it just as if it were a namespace object. For example:
328
+
329
+ Mix in `NsOptions::Proxy` to any module/class to make it proxy a namespace. This essentially turns your receiver into a namespace - you can interact with it just as if it were a namespace object. For example:
448
330
 
449
331
  ```ruby
450
332
  module Something
@@ -469,7 +351,7 @@ Something.each do |opt_name, opt_value|
469
351
  end
470
352
  ```
471
353
 
472
- What's great is that while your Something behaves like a namespace, you can still define methods and add to it just as you would normally in Ruby:
354
+ While your `Something` behaves like a namespace, you can still define methods and add to it just as you would normally in Ruby:
473
355
 
474
356
  ```ruby
475
357
  module Something
@@ -493,9 +375,7 @@ end
493
375
 
494
376
  ### Proxy initialization
495
377
 
496
- Mixing in Proxy with mixin a default initializer for you as well. This initializer allows you to call `new` on your proxy, passing it a hash of key-values. These key values will be applied to the proxy using the `Namespace#apply` logic. This allows you to use Proxy objects as option types and maintain the option type-casting and defaulting behavior.
497
-
498
- A Module example
378
+ Mixing in Proxy will add a default initializer for you as well. This initializer allows you to call `new` on your proxy, passing it a hash of key-values. These key values will be applied to the proxy using the `Namespace#apply` logic. This allows you to use Proxy objects as option types and maintain the option type-casting and defaulting behavior.
499
379
 
500
380
  ```ruby
501
381
  module Things
@@ -513,18 +393,24 @@ t = Thing.new(:one => 1, :two => 2, :three => 3)
513
393
  t.to_hash # => {:one => 1, :two => 2, :three => 3}
514
394
  ```
515
395
 
516
- # A Class example
396
+ ## Installation
517
397
 
518
- ```
519
- class Thing
520
- include NsOptions::Proxy
398
+ Add this line to your application's Gemfile:
521
399
 
522
- option :one
523
- option :two
524
- end
400
+ gem 'ns-options'
525
401
 
526
- # proxy defines an `initialize` method that takes a hash arg and
527
- # applies it to the proxy
528
- t = Thing.new(:one => 1, :two => 2, :three => 3)
529
- t.to_hash # => {:one => 1, :two => 2, :three => 3}
530
- ```
402
+ And then execute:
403
+
404
+ $ bundle
405
+
406
+ Or install it yourself as:
407
+
408
+ $ gem install ns-options
409
+
410
+ ## Contributing
411
+
412
+ 1. Fork it
413
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
414
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
415
+ 4. Push to the branch (`git push origin my-new-feature`)
416
+ 5. Create new Pull Request