golly-utils 0.0.1 → 0.6.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.
- data/.corvid/Gemfile +28 -0
- data/.corvid/features.yml +4 -0
- data/.corvid/plugins.yml +4 -0
- data/.corvid/stats_cfg.rb +13 -0
- data/.corvid/todo_cfg.rb +15 -0
- data/.corvid/versions.yml +2 -0
- data/.simplecov +6 -3
- data/CHANGELOG.md +45 -2
- data/Gemfile +6 -3
- data/Gemfile.lock +34 -37
- data/Guardfile +10 -4
- data/RELEASE.md +2 -0
- data/Rakefile +1 -1
- data/golly-utils.gemspec +19 -10
- data/lib/golly-utils/attr_declarative.rb +120 -49
- data/lib/golly-utils/callbacks.rb +211 -19
- data/lib/golly-utils/child_process.rb +28 -8
- data/lib/golly-utils/delegator.rb +14 -3
- data/lib/golly-utils/ruby_ext/classes_and_types.rb +120 -0
- data/lib/golly-utils/ruby_ext/enumerable.rb +16 -0
- data/lib/golly-utils/ruby_ext/env_helpers.rb +17 -0
- data/lib/golly-utils/ruby_ext/kernel.rb +18 -0
- data/lib/golly-utils/ruby_ext/options.rb +28 -0
- data/lib/golly-utils/ruby_ext/pretty_error_messages.rb +1 -1
- data/lib/golly-utils/singleton.rb +130 -0
- data/lib/golly-utils/testing/dynamic_fixtures.rb +268 -0
- data/lib/golly-utils/testing/file_helpers.rb +117 -0
- data/lib/golly-utils/testing/helpers_base.rb +20 -0
- data/lib/golly-utils/testing/rspec/arrays.rb +85 -0
- data/lib/golly-utils/testing/rspec/base.rb +9 -0
- data/lib/golly-utils/testing/rspec/deferrable_specs.rb +111 -0
- data/lib/golly-utils/testing/rspec/files.rb +262 -0
- data/lib/golly-utils/{test/spec → testing/rspec}/within_time.rb +17 -7
- data/lib/golly-utils/version.rb +2 -1
- data/test/bootstrap/all.rb +8 -1
- data/test/bootstrap/spec.rb +1 -1
- data/test/bootstrap/unit.rb +1 -1
- data/test/spec/child_process_spec.rb +1 -1
- data/test/spec/testing/dynamic_fixtures_spec.rb +131 -0
- data/test/spec/testing/rspec/arrays_spec.rb +33 -0
- data/test/spec/testing/rspec/files_spec.rb +300 -0
- data/test/unit/attr_declarative_test.rb +79 -13
- data/test/unit/callbacks_test.rb +103 -5
- data/test/unit/delegator_test.rb +25 -1
- data/test/unit/ruby_ext/classes_and_types_test.rb +103 -0
- data/test/unit/ruby_ext/enumerable_test.rb +12 -0
- data/test/unit/ruby_ext/options_test.rb +29 -0
- data/test/unit/singleton_test.rb +59 -0
- metadata +100 -10
- data/Gemfile.corvid +0 -27
- data/lib/golly-utils/ruby_ext.rb +0 -2
- data/lib/golly-utils/ruby_ext/subclasses.rb +0 -17
- data/lib/golly-utils/test/spec/deferrable_specs.rb +0 -85
- data/test/unit/ruby_ext/subclasses_test.rb +0 -24
@@ -1,22 +1,158 @@
|
|
1
1
|
require 'golly-utils/ruby_ext/deep_dup'
|
2
|
+
require 'golly-utils/delegator'
|
2
3
|
|
3
4
|
module GollyUtils
|
5
|
+
# A very simple callback mechanism for use within a single class heirarchy.
|
6
|
+
#
|
7
|
+
# It is primiarily meant to be used as a replacement for method overriding in external subclasses; the problem with
|
8
|
+
# that approach being a) it's unclear with methods are required/overrides, and b) if the wrong method name is used
|
9
|
+
# there is no early feedback - the erronously named method will simply never be invoked and the super-method will not
|
10
|
+
# receive the intended modification.
|
11
|
+
#
|
12
|
+
# It allows:
|
13
|
+
#
|
14
|
+
# 1. A class to define named callback point.
|
15
|
+
# 2. Subclasses to supply callbacks to specific points by name.
|
16
|
+
# 3. Ability to run all callbacks for a given callback point.
|
17
|
+
#
|
18
|
+
# Unlike Rails' callbacks implementation, this deliberately doesn't provide before/after/around functionality, nor a
|
19
|
+
# chain-like structure where the return value of one callback can affect the determinism of other callbacks being
|
20
|
+
# invoked.
|
21
|
+
#
|
22
|
+
# ## Usage
|
23
|
+
#
|
24
|
+
# * In your superclass:
|
25
|
+
# 1. Include {Callbacks}.
|
26
|
+
# 2. Use {ClassMethods#define_callbacks} in the class definition.
|
27
|
+
# 3. Call {InstanceMethods#run_callbacks} in your code.
|
28
|
+
# * In subclasses:
|
29
|
+
# 1. Supply a callback by declaring the callback name in the class definition, followed by a block of code.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# class Engine
|
33
|
+
# include GollyUtils::Callbacks
|
34
|
+
#
|
35
|
+
# define_callback :start
|
36
|
+
#
|
37
|
+
# def start
|
38
|
+
# puts "About to start..."
|
39
|
+
# run_callbacks :start
|
40
|
+
# puts "Running."
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# class CustomEngine < Engine
|
45
|
+
# start do
|
46
|
+
# puts "---> STARTING!!!"
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# CustomEngine.new.start # => About to start...
|
51
|
+
# # => ---> STARTING!!!
|
52
|
+
# # => Running.
|
53
|
+
#
|
54
|
+
# @example Also works in modules
|
55
|
+
# module SupportsStuff
|
56
|
+
# include GollyUtils::Callbacks
|
57
|
+
# define_callback :stuff
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# class DoerOfStuff
|
61
|
+
# include SupportsStuff
|
62
|
+
# stuff{ puts 'Doing stuff!!' }
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# def stuff_machine(anything_that_supports_stuff)
|
66
|
+
# puts "I'll take anything that SupportsStuff."
|
67
|
+
# anything_that_supports_stuff.run_callbacks :stuff
|
68
|
+
# puts "See!"
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# stuff_machine DoerOfStuff.new # => I'll take anything that SupportsStuff.
|
72
|
+
# # => Doing stuff!!
|
73
|
+
# # => See!
|
4
74
|
module Callbacks
|
5
75
|
|
76
|
+
# @!visibility private
|
6
77
|
def self.included(base)
|
7
|
-
base.
|
8
|
-
|
9
|
-
|
10
|
-
|
78
|
+
if base.is_a?(Class)
|
79
|
+
base.send :include, InstanceMethods
|
80
|
+
base.extend ClassMethods
|
81
|
+
else
|
82
|
+
base.extend ModuleMethods
|
83
|
+
base.class_eval <<-EOB
|
84
|
+
class << self
|
85
|
+
alias :included_without_gu_callbacks :included
|
86
|
+
def included(base)
|
87
|
+
included_without_gu_callbacks(base)
|
88
|
+
__add_callbacks_when_included(base)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
EOB
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def self.__norm_callback_key(key)
|
97
|
+
key.to_sym
|
98
|
+
end
|
99
|
+
|
100
|
+
#-------------------------------------------------------------------------------------------------------------------
|
101
|
+
|
102
|
+
# Provides methods that can be run within definitions of modules that include {Callbacks}.
|
103
|
+
module ModuleMethods
|
104
|
+
|
105
|
+
# Create one or more callback points that will be added to classes that include the enclosing module.
|
106
|
+
#
|
107
|
+
# @param (see GollyUtils::Callbacks::ClassMethods#define_callbacks)
|
108
|
+
# @return [true]
|
109
|
+
def define_callbacks(*callbacks)
|
110
|
+
__module_callback_names.concat callbacks
|
111
|
+
__module_callback_names.uniq!
|
112
|
+
true
|
113
|
+
end
|
114
|
+
alias :define_callback :define_callbacks
|
115
|
+
|
116
|
+
# Returns a list of all callbacks available to this module. (i.e. defined, inherited, and included.)
|
117
|
+
#
|
118
|
+
# @return [Array<Symbol>] Callback names.
|
119
|
+
def callbacks
|
120
|
+
c= __module_callback_names
|
121
|
+
included_modules.each {|m|
|
122
|
+
c.concat m.callbacks if m.respond_to? :callbacks
|
123
|
+
}
|
124
|
+
c.uniq.sort_by(&:to_s)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
def __add_callbacks_when_included(base)
|
129
|
+
base.send :include, Callbacks
|
130
|
+
names= __module_callback_names
|
131
|
+
unless names.empty?
|
132
|
+
base.class_eval "define_callbacks *#{names.inspect}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def __module_callback_names
|
137
|
+
@__module_callbacks ||= []
|
138
|
+
end
|
11
139
|
end
|
12
140
|
|
13
141
|
#-------------------------------------------------------------------------------------------------------------------
|
14
142
|
|
143
|
+
# Provides methods that can be run within definitions of classes that include {Callbacks}.
|
15
144
|
module ClassMethods
|
16
145
|
|
146
|
+
# Create one or more callback points for this class and its children.
|
147
|
+
#
|
148
|
+
# @param [Array<String|Symbol>] callbacks The callback name(s).
|
149
|
+
# @return [true]
|
150
|
+
# @raise If the callback has already been defined, or a method with that name already exists.
|
151
|
+
# @see Callbacks
|
152
|
+
# @see InstanceMethods#run_callbacks
|
17
153
|
def define_callbacks(*callbacks)
|
18
154
|
callbacks.each do |name|
|
19
|
-
name=
|
155
|
+
name= ::GollyUtils::Callbacks.__norm_callback_key(name)
|
20
156
|
|
21
157
|
if self.methods.include?(name.to_sym)
|
22
158
|
raise "Can't create callback with name '#{name}'. A method with that name already exists."
|
@@ -30,9 +166,19 @@ module GollyUtils
|
|
30
166
|
end
|
31
167
|
EOB
|
32
168
|
end
|
169
|
+
true
|
33
170
|
end
|
34
171
|
alias :define_callback :define_callbacks
|
35
172
|
|
173
|
+
# Returns a list of all callbacks available to this class. (i.e. defined, inherited, and included.)
|
174
|
+
#
|
175
|
+
# @return [Array<Symbol>] Callback names.
|
176
|
+
def callbacks
|
177
|
+
c= superclass.respond_to?(:callbacks) ? superclass.callbacks : []
|
178
|
+
c.concat _callbacks.keys
|
179
|
+
c.uniq.sort_by(&:to_s)
|
180
|
+
end
|
181
|
+
|
36
182
|
private
|
37
183
|
|
38
184
|
def _callbacks
|
@@ -63,29 +209,75 @@ module GollyUtils
|
|
63
209
|
|
64
210
|
#-------------------------------------------------------------------------------------------------------------------
|
65
211
|
|
66
|
-
|
212
|
+
# Provides methods that are available to instances of classes that include {Callbacks}.
|
213
|
+
module InstanceMethods
|
67
214
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
215
|
+
# Run all callbacks provided for a single callback point.
|
216
|
+
#
|
217
|
+
# @param [String, Symbol] callback The callback name.
|
218
|
+
# @param [Hash] options
|
219
|
+
# @option options [Array] args ([]) Arguments to pass to the callbacks.
|
220
|
+
# @option options [nil|Object] context (nil) If provided, code within callbacks will have access to methods
|
221
|
+
# available from the provided object.
|
222
|
+
# @return [true]
|
223
|
+
# @raise If the provided callback name hasn't been declared for this class.
|
224
|
+
# @raise If unrecognised or invalid options are provided.
|
225
|
+
# @see Callbacks
|
226
|
+
# @see ClassMethods#define_callbacks
|
227
|
+
def run_callback(callback, options={})
|
72
228
|
|
73
|
-
|
229
|
+
# Validate callback name
|
230
|
+
name= ::GollyUtils::Callbacks.__norm_callback_key(callback)
|
231
|
+
name_verified,callback_procs = self.class.send :_get_callback_procs, name
|
232
|
+
raise "There is no callback defined with name #{name}." unless name_verified
|
74
233
|
|
75
|
-
|
234
|
+
# Validate options
|
235
|
+
invalid_options= options.keys - RUN_CALLBACKS_OPTIONS
|
236
|
+
unless invalid_options.empty?
|
237
|
+
raise "Unable to recognise options: #{invalid_options.map(&:inspect).sort}"
|
238
|
+
end
|
239
|
+
args= options[:args] || []
|
240
|
+
raise "The :args option must provide an array. Invalid: #{args}" unless args.is_a?(Array)
|
76
241
|
|
77
|
-
|
242
|
+
# Run callback
|
243
|
+
callback_procs.each{|cb|
|
244
|
+
if ctx= options[:context]
|
245
|
+
dlg= GollyUtils::Delegator.new self, ctx, delegate_to: :first, allow_protected: true
|
246
|
+
dlg.instance_eval &cb
|
247
|
+
else
|
248
|
+
cb.call *args
|
249
|
+
end
|
250
|
+
}
|
251
|
+
|
252
|
+
true
|
253
|
+
end
|
78
254
|
|
255
|
+
# Run all callbacks provided for one or more callback points.
|
256
|
+
#
|
257
|
+
# @overload run_callbacks(*callbacks, options = {})
|
258
|
+
# @param [Array<String, Symbol>] callbacks The callback name(s).
|
259
|
+
# @param [Hash] options
|
260
|
+
# @option options [Array] args ([]) Arguments to pass to the callbacks.
|
261
|
+
# @option options [nil|Object] context (nil) If provided, code within callbacks will have access to methods
|
262
|
+
# available from the provided object.
|
263
|
+
# @return [true]
|
264
|
+
# @raise If one of the provided callback names hasn't been declared for this class.
|
265
|
+
# @raise If unrecognised or invalid options are provided.
|
266
|
+
# @see Callbacks
|
267
|
+
# @see ClassMethods#define_callbacks
|
79
268
|
def run_callbacks(*callbacks)
|
80
|
-
callbacks.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
269
|
+
options= callbacks.last.is_a?(Hash) ? callbacks.pop : {}
|
270
|
+
|
271
|
+
# Run callbacks
|
272
|
+
callbacks.each do |callback|
|
273
|
+
run_callback callback, options
|
85
274
|
end
|
275
|
+
|
86
276
|
true
|
87
277
|
end
|
88
|
-
|
278
|
+
|
279
|
+
# @!visibility private
|
280
|
+
RUN_CALLBACKS_OPTIONS= [:args, :context].freeze
|
89
281
|
|
90
282
|
end
|
91
283
|
end
|
@@ -2,15 +2,33 @@ module GollyUtils
|
|
2
2
|
|
3
3
|
# Start, manage, and stop a child process.
|
4
4
|
class ChildProcess
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
# The shell command to start the child process.
|
7
|
+
# @return [String]
|
8
|
+
attr_accessor :start_command
|
9
|
+
|
10
|
+
# Whether to print startup/shutdown info to stdout, and whether or not to the stdout and stderr streams of the child
|
11
|
+
# process (unless explictly redirected via :spawn_options)
|
12
|
+
# @return [Boolean]
|
13
|
+
attr_accessor :quiet
|
7
14
|
alias :quiet? :quiet
|
8
15
|
|
9
|
-
#
|
10
|
-
# @
|
11
|
-
|
12
|
-
|
13
|
-
#
|
16
|
+
# Options to pass to `Process#spawn`.
|
17
|
+
# @return [Hash]
|
18
|
+
attr_accessor :spawn_options
|
19
|
+
|
20
|
+
# Environment variables to set in the child process.
|
21
|
+
# @return [Hash]
|
22
|
+
attr_accessor :env
|
23
|
+
|
24
|
+
# The PID of the child process if running.
|
25
|
+
# @return [Fixnum, nil]
|
26
|
+
attr_reader :pid
|
27
|
+
|
28
|
+
# @option options [String] :start_command See {#start_command}.
|
29
|
+
# @option options [Hash] :env See {#env}.
|
30
|
+
# @option options [Boolean] :quiet (false) See {#quiet}.
|
31
|
+
# @option options [Hash] :spawn_options See {#spawn_options}.
|
14
32
|
def initialize(options={})
|
15
33
|
options= {env: {}, quiet: false, spawn_options: {}}.merge(options)
|
16
34
|
options[:spawn_options][:in] ||= '/dev/null'
|
@@ -21,7 +39,7 @@ module GollyUtils
|
|
21
39
|
#
|
22
40
|
# If it is already running, then this will do nothing.
|
23
41
|
#
|
24
|
-
# @return
|
42
|
+
# @return [self]
|
25
43
|
def startup
|
26
44
|
unless alive?
|
27
45
|
opt= self.spawn_options
|
@@ -103,12 +121,14 @@ module GollyUtils
|
|
103
121
|
|
104
122
|
# --------------------------------------------------------------------------------------------------------------------
|
105
123
|
class << self
|
124
|
+
# @!visibility private
|
106
125
|
OPTION_TRANSLATION= {
|
107
126
|
stdout: :out,
|
108
127
|
stderr: :err,
|
109
128
|
stdin: :in,
|
110
129
|
}.freeze
|
111
130
|
|
131
|
+
# @!visibility private
|
112
132
|
def translate_options(prefix='')
|
113
133
|
spawn_opts= {}
|
114
134
|
OPTION_TRANSLATION.each do |from,to|
|
@@ -7,6 +7,8 @@ module GollyUtils
|
|
7
7
|
# @overload initialize(*delegates, options={})
|
8
8
|
# @param [Object] delegates Objects that method calls may be delegated to.
|
9
9
|
# @param [Hash] options
|
10
|
+
# @option options [true,false] :allow_protected (false) Whether or not to allow calls to protected methods in
|
11
|
+
# delegates.
|
10
12
|
# @option options [true,false] :cache (true) Whether or not to maintain a cache of which delegate objects can
|
11
13
|
# respond to each method call.
|
12
14
|
# @option options [:first,:all] :delegate_to (:first) When multiple delegates can respond to a method call, this
|
@@ -22,6 +24,7 @@ module GollyUtils
|
|
22
24
|
@delegates= args
|
23
25
|
@delegate_to= options[:delegate_to] || :first
|
24
26
|
@cache= {} unless options.has_key?(:cache) && !options[:cache]
|
27
|
+
@allow_protected= options[:allow_protected]
|
25
28
|
parse_method_delegation_option options, :method_whitelist
|
26
29
|
parse_method_delegation_option options, :method_blacklist
|
27
30
|
end
|
@@ -35,20 +38,28 @@ module GollyUtils
|
|
35
38
|
|
36
39
|
case delegate_to
|
37
40
|
when :first
|
38
|
-
matches[0]
|
41
|
+
delegate_call matches[0], method, args
|
39
42
|
when :all
|
40
|
-
matches.map{|m| m
|
43
|
+
matches.map{|m| delegate_call m, method, args }
|
41
44
|
else
|
42
45
|
raise "Don't know how to respond to :delegate_to value of #{delegate_to.inspect}"
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
46
49
|
def respond_to?(method)
|
47
|
-
!delegates_that_respond_to(method).empty?
|
50
|
+
super(method) or !delegates_that_respond_to(method).empty?
|
48
51
|
end
|
49
52
|
|
50
53
|
private
|
51
54
|
|
55
|
+
def delegate_call(target, method, args)
|
56
|
+
if @allow_protected and target.protected_methods.include?(method)
|
57
|
+
target.send method, *args
|
58
|
+
else
|
59
|
+
target.public_send method, *args
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
52
63
|
def parse_method_delegation_option(options, name)
|
53
64
|
if values= options[name]
|
54
65
|
methods= [values].flatten.compact.map{|m| m.is_a?(String) ? m.to_sym : m}.uniq
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class Class
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
def inherited other
|
5
|
+
super if defined? super
|
6
|
+
ensure
|
7
|
+
( @subclasses ||= [] ).push(other).uniq!
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a list of classes that extend this class, directly or indirectly (as in subclasses of subclasses).
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # Given the following class heirarchy:
|
14
|
+
# # A
|
15
|
+
# # |
|
16
|
+
# # +--B
|
17
|
+
# # | +--B1
|
18
|
+
# # | +--B2
|
19
|
+
# # |
|
20
|
+
# # +--C
|
21
|
+
#
|
22
|
+
# A.subclasses # => [B1, B2, C]
|
23
|
+
# A.subclasses(false) # => [B1, B2, C]
|
24
|
+
# A.subclasses(true) # => [B, B1, B2, C]
|
25
|
+
#
|
26
|
+
# @param include_subclassed_nodes If `true` then classes extended by other classes are returned. If `false` then you
|
27
|
+
# only get the end nodes.
|
28
|
+
# @return [Array<Class>] An array of all subclasses.
|
29
|
+
def subclasses(include_subclassed_nodes = false)
|
30
|
+
@subclasses ||= []
|
31
|
+
classes= @subclasses.inject( [] ) {|list, subclass| list.push subclass, *subclass.subclasses }
|
32
|
+
classes.reject! {|c| classes.any?{|i| c != i and c.subclasses.include?(i) }} unless include_subclassed_nodes
|
33
|
+
classes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Object
|
38
|
+
# Returns the class hierarchy of a given instance or class.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Fixnum.superclasses # <= [Fixnum, Integer, Numeric, Object, BasicObject]
|
42
|
+
# 100.superclasses # <= [Fixnum, Integer, Numeric, Object, BasicObject]
|
43
|
+
#
|
44
|
+
# @return [Array<Class>] An array of classes starting with the current class, descending to `BasicObject`.
|
45
|
+
def superclasses
|
46
|
+
if self == BasicObject
|
47
|
+
[self]
|
48
|
+
elsif self.is_a? Class
|
49
|
+
[self] + self.superclass.superclasses
|
50
|
+
else
|
51
|
+
self.class.superclasses
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Indicates that a type validation check has failed.
|
56
|
+
class TypeValidationError < RuntimeError
|
57
|
+
end
|
58
|
+
|
59
|
+
# Validates the type of the current object.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# 3 .validate_type nil, Numeric
|
63
|
+
# nil .validate_type nil, Numeric
|
64
|
+
# 'What'.validate_type nil, Numeric # <= raises TypeValidationError
|
65
|
+
#
|
66
|
+
# # Ensures that f_debug is boolean
|
67
|
+
# f_debug.validate_type! 'the debug flag', true, false
|
68
|
+
#
|
69
|
+
# @overload validate_type!(name = nil, *valid_classes)
|
70
|
+
# @param [nil|Symbol|String] name The name of the object being checked (i.e. `self`).
|
71
|
+
# This only used in the error message and has no functional impact.
|
72
|
+
# @param [Array<Class>] valid_classes One or more classes that this object is allowed to be. Ruby primatives will
|
73
|
+
# automatically be translated to the corresponding class.
|
74
|
+
# @return [self] If validation passes.
|
75
|
+
# @raise [TypeValidationError] If validation fails.
|
76
|
+
# @see Symbol#validate_lvar_type!
|
77
|
+
def validate_type!(*args)
|
78
|
+
name= args.first.is_a?(String) || args.first.is_a?(Symbol) ? args.shift : nil
|
79
|
+
classes= args.map{|a| RUBY_PRIMATIVE_CLASSES[a] || a }
|
80
|
+
raise "You must specify at least one valid class." if classes.empty?
|
81
|
+
|
82
|
+
unless classes.any?{|c| self.is_a? c }
|
83
|
+
for_name= name ? " for #{name}" : ''
|
84
|
+
raise TypeValidationError, "Invalid type#{for_name}: #{self.class}\nValid types are: #{classes.map(&:to_s).sort.join ', '}."
|
85
|
+
end
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# @!visibility private
|
91
|
+
RUBY_PRIMATIVE_CLASSES= Hash[ [nil,true,false].map{|p|[p,p.class]} ].freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
class Symbol
|
95
|
+
# Validates the type of a local variable.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# def save_person(name, eyes)
|
99
|
+
# # Validate args
|
100
|
+
# :name.validate_lvar_type!{ String }
|
101
|
+
# :eyes.validate_lvar_type!{ [nil,Fixnum] }
|
102
|
+
#
|
103
|
+
# # Do other stuff
|
104
|
+
# # ...
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# @yield Calls a given block once to get the list of valid classes. The block must have access to the local variable.
|
108
|
+
# @yieldreturn [Class|Array<Class>] The given block should return one or more classes.
|
109
|
+
# @return [true] If validation passes.
|
110
|
+
# @raise [TypeValidationError] If validation fails.
|
111
|
+
# @see Object#validate_type!
|
112
|
+
def validate_lvar_type!(&block)
|
113
|
+
name= self
|
114
|
+
raise "You must provide a block that returns one or more valid classes for #{name}." unless block
|
115
|
+
classes= [block.()].flatten
|
116
|
+
v= block.send(:binding).eval(name.to_s)
|
117
|
+
v.validate_type! name, *classes
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|