golly-utils 0.0.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|