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.
- data/.gitignore +3 -0
- data/Gemfile +2 -1
- data/LICENSE +1 -1
- data/README.md +187 -301
- data/Rakefile +1 -1
- data/lib/ns-options/assert_macros.rb +9 -12
- data/lib/ns-options/boolean.rb +2 -0
- data/lib/ns-options/namespace.rb +34 -134
- data/lib/ns-options/namespace_advisor.rb +35 -0
- data/lib/ns-options/namespace_data.rb +166 -0
- data/lib/ns-options/namespaces.rb +23 -12
- data/lib/ns-options/option.rb +50 -24
- data/lib/ns-options/options.rb +23 -49
- data/lib/ns-options/proxy.rb +40 -53
- data/lib/ns-options/proxy_method.rb +54 -0
- data/lib/ns-options/root_methods.rb +77 -0
- data/lib/ns-options/version.rb +1 -1
- data/lib/ns-options.rb +18 -8
- data/ns-options.gemspec +3 -4
- data/test/helper.rb +3 -10
- data/test/support/app.rb +3 -1
- data/test/support/proxy.rb +4 -0
- data/test/support/type_class_proxy.rb +29 -0
- data/test/support/user.rb +5 -5
- data/test/{integration/app_test.rb → system/app_tests.rb} +8 -6
- data/test/{integration/proxy_test.rb → system/proxy_tests.rb} +12 -0
- data/test/system/type_class_proxy_tests.rb +108 -0
- data/test/system/user_tests.rb +146 -0
- data/test/unit/{ns-options/boolean_test.rb → boolean_tests.rb} +5 -4
- data/test/unit/namespace_advisor_tests.rb +69 -0
- data/test/unit/namespace_data_tests.rb +336 -0
- data/test/unit/namespace_tests.rb +205 -0
- data/test/unit/namespaces_tests.rb +99 -0
- data/test/unit/{ns-options/option_test.rb → option_tests.rb} +155 -93
- data/test/unit/options_tests.rb +152 -0
- data/test/unit/proxy_method_tests.rb +87 -0
- data/test/unit/{ns-options/proxy_test.rb → proxy_tests.rb} +52 -0
- data/test/unit/root_methods_tests.rb +126 -0
- metadata +58 -63
- data/lib/ns-options/errors/invalid_name.rb +0 -15
- data/lib/ns-options/has_options.rb +0 -53
- data/lib/ns-options/helper/advisor.rb +0 -88
- data/lib/ns-options/helper.rb +0 -87
- data/test/integration/user_test.rb +0 -94
- data/test/unit/ns-options/has_options_test.rb +0 -90
- data/test/unit/ns-options/helper/advisor_test.rb +0 -148
- data/test/unit/ns-options/helper_test.rb +0 -56
- data/test/unit/ns-options/namespace_test.rb +0 -432
- data/test/unit/ns-options/namespaces_test.rb +0 -55
- data/test/unit/ns-options/options_test.rb +0 -221
- /data/test/unit/{ns-options/assert_macros_test.rb → assert_macros_tests.rb} +0 -0
data/Rakefile
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/ns-options/boolean.rb
CHANGED
data/lib/ns-options/namespace.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
self
|
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
|
-
|
23
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
80
|
-
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
99
|
-
|
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)}:#{
|
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
|
-
|
1
|
+
require 'ns-options/namespace'
|
2
2
|
|
3
|
-
|
3
|
+
module NsOptions
|
4
|
+
class Namespaces
|
4
5
|
|
5
|
-
def
|
6
|
-
|
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
|
-
|
13
|
-
|
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
|
-
|
17
|
-
|
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
|
data/lib/ns-options/option.rb
CHANGED
@@ -1,26 +1,38 @@
|
|
1
1
|
module NsOptions
|
2
2
|
|
3
3
|
class Option
|
4
|
-
|
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(
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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]
|
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
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|