ns-options 0.4.1 → 1.0.0.rc1

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.
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/Rakefile CHANGED
@@ -2,4 +2,4 @@
2
2
  require "bundler/gem_tasks"
3
3
 
4
4
  require 'assert/rake_tasks'
5
- Assert::RakeTasks.for :test
5
+ Assert::RakeTasks.install
@@ -1,13 +1,12 @@
1
- module NsOptions; end
1
+ require 'ns-options'
2
+
2
3
  module NsOptions::AssertMacros
3
4
 
4
5
  # a set of Assert macros to help write namespace definition and
5
6
  # regression tests in Assert (https://github.com/teaminsight/assert)
6
7
 
7
8
  def self.included(receiver)
8
- receiver.class_eval do
9
- extend MacroMethods
10
- end
9
+ receiver.class_eval { extend MacroMethods }
11
10
  end
12
11
 
13
12
  module MacroMethods
@@ -19,8 +18,7 @@ module NsOptions::AssertMacros
19
18
  Assert::Macro.new(macro_name) do
20
19
  namespaces.each do |ns|
21
20
  should "have a namespace named '#{ns}'", called_from do
22
- assert_respond_to ns, subject
23
- assert_kind_of NsOptions::Namespace, subject.send(ns)
21
+ assert subject.has_namespace?(ns)
24
22
  end
25
23
  end
26
24
  end
@@ -34,8 +32,7 @@ module NsOptions::AssertMacros
34
32
  Assert::Macro.new(macro_name) do
35
33
  options.each do |opt|
36
34
  should "have an option named '#{opt}'", called_from do
37
- assert_respond_to opt, subject
38
- assert_kind_of NsOptions::Option, subject.options[opt]
35
+ assert subject.has_option?(opt)
39
36
  end
40
37
  end
41
38
  end
@@ -43,7 +40,7 @@ module NsOptions::AssertMacros
43
40
 
44
41
  def have_option(*args)
45
42
  called_from = caller.first
46
- rules, type_class, opt_name = NsOptions::Option.args(*args)
43
+ opt_name, type_class, rules = NsOptions::Option.args(args)
47
44
  test_name = [
48
45
  "have an option: '#{opt_name}'",
49
46
  "of type '#{type_class}'",
@@ -53,10 +50,10 @@ module NsOptions::AssertMacros
53
50
  Assert::Macro.new(test_name) do
54
51
 
55
52
  should test_name do
56
- # name assertions
57
- assert_respond_to opt_name, subject
58
53
 
59
- opt = subject.options[opt_name]
54
+ # have assertions
55
+ assert subject.has_option?(opt_name)
56
+ opt = subject.__data__.child_options[opt_name]
60
57
  assert_kind_of NsOptions::Option, opt
61
58
 
62
59
  # type_class assertions
@@ -1,3 +1,5 @@
1
+ require 'ns-options'
2
+
1
3
  module NsOptions
2
4
  class Boolean
3
5
 
@@ -1,119 +1,54 @@
1
+ require 'ns-options/namespace_data'
2
+ require 'ns-options/namespace_advisor'
3
+
1
4
  module NsOptions
2
5
 
3
6
  class Namespace
4
- attr_accessor :options, :metaclass
5
7
 
6
- # Every namespace tracks a metaclass to allow for individual reader/writers for their options,
7
- # without any collisions. Since every namespace is of the same class, defining option reader and
8
- # writer methods directly on the class would make multiple namespaces with different options
9
- # impossible.
10
- def initialize(key, parent = nil, &block)
11
- self.metaclass = (class << self; self; end)
12
- self.options = NsOptions::Options.new(key, parent)
13
- self.define(&block)
14
- end
8
+ attr_reader :__name__, :__data__
15
9
 
16
- # This is a helper to check if options that were defined as :required have been set.
17
- def required_set?
18
- self.options.required_set?
10
+ def initialize(name, option_type_class=nil, &block)
11
+ @__name__ = name
12
+ @__data__ = NamespaceData.new(self, option_type_class || Object)
13
+ @__data__.define(&block)
19
14
  end
20
- alias :valid? :required_set?
21
15
 
22
- # Define an option for this namespace. Add the option to the namespace's options collection
23
- # and then define accessors for the option. With the following:
24
- #
25
- # namespace.option(:root, String, { :some_option => true })
26
- #
27
- # you will get accessors for root:
28
- #
29
- # namespace.root = "something" # set's the root option to 'something'
30
- # namespace.root # => "something"
31
- # namespace.root("something else") # set's the root option to `something-else`
32
- #
33
- # The defined option is returned as well.
34
- def option(*args)
35
- NsOptions::Helper.advisor(self).is_this_option_ok?(args[0], caller)
36
- option = self.options.add(*args)
37
- NsOptions::Helper.define_option_methods(self, option)
38
- option
16
+ def option(name, *args)
17
+ NamespaceAdvisor.new(@__data__, name, 'an option').run($stdout, caller)
18
+ @__data__.add_option(name, *args)
39
19
  end
40
20
  alias_method :opt, :option
41
21
 
42
- # Define a namespace under this namespace. Firstly, a new key is constructured from this current
43
- # namespace's key and the name for the new namespace. The namespace is then added to the
44
- # options collection. Finally a reader method is defined for accessing the namespace. With the
45
- # following:
46
- #
47
- # parent_namespace.namespace(:specific) do
48
- # option :root
49
- # end
50
- #
51
- # you will get a reader for the namespace:
52
- #
53
- # parent_namespace.specific # => returns the namespace
54
- # parent_namespace.specific.root = "something" # => options are accessed in the same way
55
- #
56
- # The defined namespaces is returned as well.
57
- def namespace(name, key = nil, &block)
58
- key = "#{self.options.key}:#{(key || name)}"
59
- NsOptions::Helper.advisor(self).is_this_sub_namespace_ok?(name, caller)
60
- namespace = self.options.add_namespace(name, key, self, &block)
61
- NsOptions::Helper.define_namespace_methods(self, name)
62
- namespace
22
+ def namespace(name, *args, &block)
23
+ NamespaceAdvisor.new(@__data__, name, 'a namespace').run($stdout, caller)
24
+ @__data__.add_namespace(name, *args, &block)
63
25
  end
64
26
  alias_method :ns, :namespace
65
27
 
66
- # The opposite of #to_hash. Takes a hash representation of options and namespaces and mass
67
- # assigns option values.
68
- def apply(option_values = {})
69
- option_values.each do |name, value|
70
- namespace = self.options.namespaces[name]
71
- if self.options[name] || !namespace
72
- self.send("#{name}=", value)
73
- elsif namespace && value.kind_of?(Hash)
74
- namespace.apply(value)
75
- end
76
- end
28
+ def option_type_class(*args)
29
+ return @__data__.option_type_class if args.empty?
30
+ @__data__.set_option_type_class(*args)
77
31
  end
32
+ alias_method :opt_type_class, :option_type_class
78
33
 
79
- # return a hash representation of the namespace
80
- # use symbols for the hash
81
- def to_hash
82
- Hash.new.tap do |out|
83
- self.options.each do |name, opt|
84
- out[name.to_sym] = opt.value
85
- end
86
- self.options.namespaces.each do |name, value|
87
- out[name.to_sym] = value.to_hash
88
- end
89
- end
90
- end
34
+ def has_option?(name); @__data__.has_option?(name); end
35
+ def has_namespace?(name); @__data__.has_namespace?(name); end
36
+ def required_set?; @__data__.required_set?; end
37
+ alias_method :valid?, :required_set?
38
+
39
+ def define(*args, &block); @__data__.define(*args, &block); end
40
+ def build_from(other_ns); @__data__.build_from(other_ns.__data__); end
41
+ def reset(*args, &block); @__data__.reset(*args, &block); end
42
+ def apply(*args, &block); @__data__.apply(*args, &block); end
43
+ def to_hash(*args, &block); @__data__.to_hash(*args, &block); end
44
+ def each(*args, &block); @__data__.each(*args, &block); end
91
45
 
92
- # allow for iterating over the key/values of a namespace
93
- # this uses #to_hash so you won't get option/namespace objs for the values
94
- def each
95
- self.to_hash.each { |k,v| yield k,v if block_given? }
46
+ def respond_to?(meth)
47
+ @__data__.ns_respond_to?(meth) || super
96
48
  end
97
49
 
98
- # The define method is provided for convenience and commonization. The internal system
99
- # uses it to commonly use a block with a namespace. The method can be used externally when
100
- # a namespace is created separately from where options are added/set on it. For example:
101
- #
102
- # parent_namespace.namespace(:specific)
103
- #
104
- # parent_namespace.specific.define do
105
- # option :root
106
- # end
107
- #
108
- # Will define a new namespace under the parent namespace and then will later on add options to
109
- # it.
110
- def define(&block)
111
- if block && block.arity > 0
112
- yield self
113
- elsif block
114
- self.instance_eval(&block)
115
- end
116
- self
50
+ def method_missing(meth, *args, &block)
51
+ @__data__.ns_method_missing(caller, meth, *args, &block)
117
52
  end
118
53
 
119
54
  def ==(other_ns)
@@ -124,43 +59,8 @@ module NsOptions
124
59
  end
125
60
  end
126
61
 
127
- # There are a number of cases we want to watch for:
128
- # 1. A reader of a 'known' option. This case is for an option that's been defined for an
129
- # ancestor of this namespace but not directly for this namespace. In this case we fetch
130
- # the options definition and use it to define the option directly for this namespace.
131
- # 2. TODO
132
- # 3. A writer of a 'known' option. This case is similar to the above, but instead we are
133
- # wanting to write a value. We need to fetch the option definition, define it and then
134
- # we write the option as we normally would.
135
- # 4. A dynamic writer. The option is not 'known' to the namespace, so we use the value and it's
136
- # class to define the option for this namespace. Then we just use the writer as we normally
137
- # would.
138
- def method_missing(method, *args, &block)
139
- option_name = method.to_s.gsub("=", "")
140
- value = args.size == 1 ? args[0] : args
141
- if args.empty? && self.respond_to?(option_name)
142
- option = NsOptions::Helper.find_and_define_option(self, option_name)
143
- self.send(option.name)
144
- elsif args.empty? && (namespace = self.options.get_namespace(option_name))
145
- NsOptions::Helper.find_and_define_namespace(self, option_name)
146
- self.send(option_name)
147
- elsif !args.empty? && self.respond_to?(option_name)
148
- option = NsOptions::Helper.find_and_define_option(self, option_name)
149
- self.send("#{option.name}=", value)
150
- elsif !args.empty?
151
- option = self.option(option_name)
152
- self.send("#{option.name}=", value)
153
- else
154
- super
155
- end
156
- end
157
-
158
- def respond_to?(method)
159
- super || self.options.is_defined?(method.to_s.gsub("=", ""))
160
- end
161
-
162
62
  def inspect(*args)
163
- "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{self.options.key} #{self.to_hash.inspect}>"
63
+ "#<#{self.class}:#{'0x%x' % (self.object_id << 1)}:#{@__name__} #{to_hash.inspect}>"
164
64
  end
165
65
 
166
66
  end
@@ -0,0 +1,35 @@
1
+ module NsOptions
2
+
3
+ class NamespaceAdvisor
4
+
5
+ def initialize(ns_data, name, kind)
6
+ @ns_data = ns_data
7
+ @name = name
8
+
9
+ @msg = if not_recommended?
10
+ "WARNING: Defining #{kind} with the name `#{@name}' overwrites a method"\
11
+ " NsOptions depends on. It may cause NsOptions to behave oddly and"\
12
+ " is not recommended."
13
+ elsif duplicate?
14
+ "WARNING: `#{@name}' has already been defined and is being overwritten."
15
+ end
16
+ end
17
+
18
+ def run(io, from_caller)
19
+ return true if @msg.nil?
20
+
21
+ io.puts @msg
22
+ io.puts from_caller.first
23
+ false
24
+ end
25
+
26
+ def not_recommended?; not_recommended_names.include?(@name.to_sym); end
27
+ def duplicate?; @ns_data.has_option?(@name) || @ns_data.has_namespace?(@name); end
28
+
29
+ def not_recommended_names
30
+ NsOptions::Namespace.instance_methods(false).map(&:to_sym)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,166 @@
1
+ require 'ns-options/options'
2
+ require 'ns-options/namespaces'
3
+
4
+ module NsOptions
5
+
6
+ class NamespaceData
7
+
8
+ attr_reader :ns, :option_type_class, :child_options, :child_namespaces
9
+
10
+ def initialize(ns, option_type_class)
11
+ @ns, @option_type_class = ns, option_type_class
12
+ @child_namespaces = NsOptions::Namespaces.new
13
+ @child_options = NsOptions::Options.new
14
+ end
15
+
16
+ # Recursively check if options that were defined as :required have been set.
17
+ def required_set?
18
+ @child_options.required_set? && @child_namespaces.required_set?
19
+ end
20
+
21
+ def set_option_type_class(value)
22
+ @option_type_class = value
23
+ end
24
+
25
+ def has_option?(name); !!@child_options[name]; end
26
+ def get_option(name); @child_options.get(name); end
27
+ def set_option(name, val); @child_options.set(name, val); end
28
+ def add_option(*args)
29
+ name = args.first
30
+ opt = NsOptions::Option.new(*NsOptions::Option.args(args, @option_type_class))
31
+
32
+ @child_namespaces.rm(name)
33
+ @child_options.add(name, opt)
34
+ end
35
+
36
+ def has_namespace?(name); !!@child_namespaces[name]; end
37
+ def get_namespace(name); @child_namespaces.get(name); end
38
+ def add_namespace(name, option_type_class=nil, &block)
39
+ opt_type_class = option_type_class || @option_type_class
40
+ ns = NsOptions::Namespace.new(name, opt_type_class, &block)
41
+
42
+ @child_options.rm(name)
43
+ @child_namespaces.add(name, ns)
44
+ end
45
+
46
+ # recursively build a hash representation of the namespace, using symbols
47
+ # for the option/namespace name-keys
48
+ def to_hash
49
+ Hash.new.tap do |hash|
50
+ @child_options.each{|name, opt| hash[name.to_sym] = opt.value}
51
+ @child_namespaces.each{|name, value| hash[name.to_sym] = value.to_hash}
52
+ end
53
+ end
54
+
55
+ # The opposite of #to_hash. Takes a hash representation of options and
56
+ # namespaces and mass assigns option values.
57
+ def apply(values=nil)
58
+ (values || {}).each do |name, value|
59
+ if has_namespace?(name)
60
+ # recursively apply namespace values if hash given; ignore otherwise.
61
+ get_namespace(name).apply(value) if value.kind_of?(Hash)
62
+ else
63
+ # be sure to use the namespace's writer to write the option value
64
+ @ns.send("#{name}=", value)
65
+ end
66
+ end
67
+ end
68
+
69
+ # allow for iterating over the key/values of a namespace
70
+ # this uses #to_hash so you won't get option/namespace objs for the values
71
+ def each
72
+ to_hash.each{|k,v| yield k,v if block_given? }
73
+ end
74
+
75
+ # define the parent ns using the given block
76
+ def define(&block)
77
+ if block && block.arity > 0
78
+ block.call @ns
79
+ elsif block
80
+ @ns.instance_eval(&block)
81
+ end
82
+ @ns
83
+ end
84
+
85
+ def build_from(other_ns_data)
86
+ set_option_type_class(other_ns_data.option_type_class)
87
+
88
+ other_ns_data.child_options.each do |name, opt|
89
+ add_option(name, opt.type_class, opt.rules)
90
+ end
91
+
92
+ other_ns_data.child_namespaces.each do |name, ns|
93
+ new_ns = add_namespace(name)
94
+ new_ns.build_from(ns)
95
+ end
96
+ end
97
+
98
+ def reset
99
+ child_options.each {|name, opt| opt.reset}
100
+ child_namespaces.each {|name, ns| ns.reset}
101
+ end
102
+
103
+ class DslMethod
104
+ attr_reader :name, :data
105
+
106
+ def initialize(meth, *args, &block)
107
+ @method_string, @args, @block = meth.to_s, args, block
108
+ @name = @method_string.gsub("=", "")
109
+ @data = args.size == 1 ? args[0] : args
110
+ end
111
+
112
+ def writer?; !!(@method_string =~ /=\Z/); end
113
+ def reader?; !self.writer?; end
114
+ def has_args?; !@args.empty?; end
115
+ end
116
+
117
+ def ns_respond_to?(meth)
118
+ dslm = DslMethod.new(meth)
119
+
120
+ has_namespace?(dslm.name) || # namespace reader
121
+ has_option?(dslm.name) || # option reader
122
+ dslm.writer? || # dynamic option writer
123
+ false
124
+ end
125
+
126
+ def ns_method_missing(bt, meth, *args, &block)
127
+ dslm = DslMethod.new(meth, *args, &block)
128
+
129
+ if is_namespace_reader?(dslm)
130
+ get_namespace(dslm.name).define(&block)
131
+ elsif is_option_reader?(dslm)
132
+ get_option(dslm.name)
133
+ elsif is_option_writer?(dslm)
134
+ # TODO: remove same-named opt/ns when adding the other with same name
135
+ add_option(dslm.name) unless has_option?(dslm.name)
136
+ begin
137
+ set_option(dslm.name, dslm.data)
138
+ rescue NsOptions::Option::CoerceError
139
+ error! bt, err # reraise this exception with a sane backtrace
140
+ end
141
+ else # namespace writer or unknown
142
+ # raise a no meth err with a sane backtrace
143
+ error! bt, NoMethodError.new("undefined method `#{meth}' for #{@ns.inspect}")
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def is_namespace_reader?(dsl_method)
150
+ has_namespace?(dsl_method.name) && !dsl_method.has_args?
151
+ end
152
+
153
+ def is_option_reader?(dsl_method)
154
+ has_option?(dsl_method.name) && !dsl_method.has_args?
155
+ end
156
+
157
+ def is_option_writer?(dsl_method)
158
+ !has_namespace?(dsl_method.name) && dsl_method.has_args?
159
+ end
160
+
161
+ def error!(backtrace, exception)
162
+ exception.set_backtrace(backtrace); raise exception
163
+ end
164
+
165
+ end
166
+ end
@@ -1,22 +1,33 @@
1
- module NsOptions
1
+ require 'ns-options/namespace'
2
2
 
3
- class Namespaces < Hash
3
+ module NsOptions
4
+ class Namespaces
4
5
 
5
- def [](name)
6
- super(name.to_sym)
7
- end
8
- def []=(name, value)
9
- super(name.to_sym, value)
6
+ def initialize
7
+ @hash = Hash.new
10
8
  end
11
9
 
12
- def add(name, key, parent = nil, &block)
13
- self[name] = NsOptions::Namespace.new(key, parent, &block)
10
+ # for hash with indifferent access behavior
11
+ def [](name); @hash[name.to_s]; end
12
+ def []=(name, value); @hash[name.to_s] = value; end
13
+
14
+ def keys(*args, &block); @hash.keys(*args, &block); end
15
+ def each(*args, &block); @hash.each(*args, &block); end
16
+ def empty?(*args, &block); @hash.empty?(*args, &block); end
17
+
18
+ def add(name, ns); self[name] = ns; end
19
+ def rm(name); @hash.delete(name.to_s); end
20
+ def get(name); self[name]; end
21
+
22
+ def required_set?
23
+ are_all_these_namespaces_set? @hash.values
14
24
  end
15
25
 
16
- def get(name)
17
- self[name]
26
+ private
27
+
28
+ def are_all_these_namespaces_set?(namespaces)
29
+ namespaces.inject(true) {|bool, ns| bool && ns.required_set?}
18
30
  end
19
31
 
20
32
  end
21
-
22
33
  end
@@ -1,26 +1,38 @@
1
1
  module NsOptions
2
2
 
3
3
  class Option
4
- attr_accessor :name, :value, :type_class, :rules
4
+
5
+ class CoerceError < ::ArgumentError
6
+ def initialize(type_class, value, err)
7
+ super("can't coerce `#{value.inspect}' to `#{type_class}': #{err.message}")
8
+ set_backtrace(err.backtrace)
9
+ end
10
+ end
5
11
 
6
12
  def self.rules(rules)
13
+ # make sure any given `:args` rule is an array
7
14
  (rules || {}).tap do |r|
8
15
  r[:args] = (r[:args] ? [*r[:args]] : [])
9
16
  end
10
17
  end
11
18
 
12
- def self.args(*args)
19
+ def self.args(args, default_type_class=nil)
13
20
  [ self.rules(args.last.kind_of?(::Hash) ? args.pop : {}),
14
21
  # if a nil type_class is given, just use Object
15
22
  # this makes the option accept any value with no type coercion
16
- (args[1] || Object),
23
+ (args[1] || default_type_class || Object),
17
24
  args[0].to_s
18
- ]
25
+ ].reverse
19
26
  end
20
27
 
21
- def initialize(*args)
22
- self.rules, self.type_class, self.name = self.class.args(*args)
23
- self.value = self.rules[:default]
28
+ attr_accessor :name, :value, :type_class, :rules
29
+
30
+ def initialize(name, type_class=nil, rules=nil)
31
+ @name, @rules = name, (rules || {})
32
+ @type_class = type_class || Object
33
+ @rules[:args] ||= []
34
+
35
+ self.reset
24
36
  end
25
37
 
26
38
  # if reading a lazy_proc, call the proc and return its coerced return val
@@ -35,10 +47,12 @@ module NsOptions
35
47
  end
36
48
  end
37
49
 
38
- # if setting a lazy_proc, just store the proc off to be called when read
39
- # otherwise, coerce and store the value being set
40
50
  def value=(new_value)
41
- @value = self.lazy_proc?(new_value) ? new_value : self.coerce(new_value)
51
+ save_value(new_value)
52
+ end
53
+
54
+ def reset
55
+ save_value(self.rules[:default])
42
56
  end
43
57
 
44
58
  def is_set?
@@ -46,7 +60,7 @@ module NsOptions
46
60
  end
47
61
 
48
62
  def required?
49
- !!self.rules[:required] || !!self.rules[:require]
63
+ !!self.rules[:required]
50
64
  end
51
65
 
52
66
  def ==(other)
@@ -57,6 +71,12 @@ module NsOptions
57
71
 
58
72
  protected
59
73
 
74
+ # if setting a lazy_proc, just store the proc off to be called when read
75
+ # otherwise, coerce and store the value being set
76
+ def save_value(new_value)
77
+ @value = self.lazy_proc?(new_value) ? new_value : self.coerce(new_value)
78
+ end
79
+
60
80
  # a value is considered to by a lazy eval proc if it some kind of a of
61
81
  # Proc and the option it is being set on is not explicitly defined as some
62
82
  # kind of Proc
@@ -67,24 +87,30 @@ module NsOptions
67
87
  end
68
88
 
69
89
  def coerce(value)
70
- return value if (value.kind_of?(self.type_class)) || value.nil?
71
-
72
- if [ Integer, Float, String ].include?(self.type_class)
73
- # ruby type conversion, i.e. String(1)
74
- Object.send(self.type_class.to_s.to_sym, value)
75
- elsif self.type_class == Symbol
76
- value.to_sym
77
- elsif self.type_class == Hash
78
- {}.merge(value)
79
- else
80
- begin
90
+ return value if no_coercing_needed?(value)
91
+
92
+ begin
93
+ if [ Integer, Float, String ].include?(self.type_class)
94
+ # ruby type conversion, i.e. String(1)
95
+ Object.send(self.type_class.to_s, value)
96
+ elsif self.type_class == Symbol
97
+ value.to_sym
98
+ elsif self.type_class == Hash
99
+ {}.merge(value)
100
+ else
81
101
  self.type_class.new(value, *self.rules[:args])
82
- rescue ArgumentError => err
83
- raise ArgumentError, "#{self.type_class} `initialize': #{err.message}"
84
102
  end
103
+ rescue Exception => err
104
+ raise CoerceError.new(self.type_class, value, err)
85
105
  end
86
106
  end
87
107
 
108
+ private
109
+
110
+ def no_coercing_needed?(value)
111
+ value.kind_of?(self.type_class) || value.nil?
112
+ end
113
+
88
114
  end
89
115
 
90
116
  end