methlab 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/methlab.rb +247 -0
- metadata +53 -0
data/lib/methlab.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
#
|
2
|
+
# MethLab - a method toolkit for ruby.
|
3
|
+
#
|
4
|
+
# MethLab is next to useless without integrating it into your classes. You can
|
5
|
+
# do this several ways:
|
6
|
+
#
|
7
|
+
# * 'extend MethLab' in your class definitions before calling any of MethLab's helpers.
|
8
|
+
# * 'MethLab.integrate' anywhere. This will inject it into ::main and ::Module.
|
9
|
+
# * set $METHLAB_AUTOINTEGRATE to true before requiring methlab. This calls 'MethLab.integrate' automatically.
|
10
|
+
#
|
11
|
+
# Please see MethLab#build_ordered and MethLab#build_named for method creation
|
12
|
+
# syntax. Note that MethLab#def_named and MethLab#def_ordered will create named
|
13
|
+
# methods in your class for you, but they use the build methods underneath the
|
14
|
+
# hood.
|
15
|
+
#
|
16
|
+
# Here's an example:
|
17
|
+
#
|
18
|
+
# class Awesome
|
19
|
+
# def_ordered(:foo, String, [Integer, :optional]) do |params|
|
20
|
+
# str, int = params
|
21
|
+
# puts "I received #{str} as a String and #{int} as an Integer!"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def_named(:bar, :foo => String, :bar => [Integer, :required]) do |params|
|
25
|
+
# puts "I received #{params[:foo]} as a String and #{params[:bar]} as an Integer!"
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Which yields these opportunities:
|
30
|
+
#
|
31
|
+
# a = Awesome.new
|
32
|
+
# a.foo(1, "str") # raises
|
33
|
+
# a.foo("str", 1) # prints the message
|
34
|
+
# a.foo("str") # prints the message with nil as the integer
|
35
|
+
#
|
36
|
+
# a.bar(:foo => 1, :bar => "str") # raises
|
37
|
+
# a.bar(:foo => "str") # raises (:bar is required)
|
38
|
+
# a.bar(:bar => 1) # prints message, with nil string
|
39
|
+
# a.bar(:foo => "str", :bar => 1) # prints message
|
40
|
+
#
|
41
|
+
# Using it is quite simple. Just remember a few things:
|
42
|
+
#
|
43
|
+
# * A class will always be compared with Object#kind_of? against the object.
|
44
|
+
# * An object implies certain semantics. Right now, we support direct checking against multiple objects:
|
45
|
+
# * Regexp's will convert the value to a string and compare them with String#=~
|
46
|
+
# * Ranges will use Range#include? to determine if the object occurs within the range.
|
47
|
+
# * A proc will allow you to do a custom check, taking one argument. Raises happen as such:
|
48
|
+
# * Returning false/nil will raise a generic error.
|
49
|
+
# * Returning a new exception object (e.g., ArgumentError.new) will raise your error as close to the call point as possible.
|
50
|
+
# * Raising yourself will raise in the validation routine, which will probably be confusing. Please use the above method.
|
51
|
+
# * If you need more than one constraint per parameter, enclose these constraints within an array.
|
52
|
+
# * Depending on the type of method you're constructing, there will be additional constraints both implied and explictly allowed:
|
53
|
+
# * named methods do not require any items by default, they must be specified as required.
|
54
|
+
# * ordered methods require everything by default, they must be specified as optional.
|
55
|
+
#
|
56
|
+
module MethLab
|
57
|
+
|
58
|
+
VERSION = "0.0.3"
|
59
|
+
|
60
|
+
# Integrates MethLab into all namespaces. It does this by patching itself
|
61
|
+
# into ::main and Module.
|
62
|
+
#
|
63
|
+
# You may also accomplish this automatically by setting
|
64
|
+
# $METHLAB_AUTOINTEGRATE before you require it.
|
65
|
+
def self.integrate
|
66
|
+
eval("self", TOPLEVEL_BINDING).send(:include, self)
|
67
|
+
::Module.send(:include, self)
|
68
|
+
end
|
69
|
+
|
70
|
+
# internal, please do not use directly.
|
71
|
+
#
|
72
|
+
# used to perform our standard checks.
|
73
|
+
def self.check_type(value_sig, value, key)
|
74
|
+
case value_sig
|
75
|
+
when Array
|
76
|
+
value_sig.flatten.each do |vs|
|
77
|
+
ret = check_type(vs, value, key)
|
78
|
+
return ret unless ret.nil?
|
79
|
+
end
|
80
|
+
when Class
|
81
|
+
unless value.kind_of?(value_sig)
|
82
|
+
return ArgumentError.new("value of argument '#{key}' is an invalid type. Requires '#{value_sig}'")
|
83
|
+
end
|
84
|
+
when Proc
|
85
|
+
ret = value_sig.call(value)
|
86
|
+
|
87
|
+
if ret.kind_of?(Exception)
|
88
|
+
return ret
|
89
|
+
elsif !ret
|
90
|
+
return ArgumentError.new("value of argument '#{key}' does not pass custom validation.")
|
91
|
+
else
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
when Regexp
|
95
|
+
unless value.to_s =~ value_sig
|
96
|
+
return ArgumentError.new("value of argument '#{key}' does not match this regexp: '#{value_sig.to_s}'")
|
97
|
+
end
|
98
|
+
when Range
|
99
|
+
unless value_sig.include?(value)
|
100
|
+
return ArgumentError.new("value of argument '#{key}' does not match range '#{value_sig.inspect}'")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
return nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# internal, do not use directly.
|
108
|
+
#
|
109
|
+
# used to check the arity of array (ordered) method calls.
|
110
|
+
def self.validate_array_params(signature, args)
|
111
|
+
unless args.kind_of?(Array)
|
112
|
+
return ArgumentError.new("this method takes ordered arguments")
|
113
|
+
end
|
114
|
+
|
115
|
+
if args.length > signature.length
|
116
|
+
return ArgumentError.new("too many arguments (#{args.length} for #{signature.length})")
|
117
|
+
end
|
118
|
+
|
119
|
+
opt_index = signature.find_index { |x| [x].flatten.include?(:optional) } || 0
|
120
|
+
|
121
|
+
if args.length < opt_index
|
122
|
+
return ArgumentError.new("not enough arguments (#{args.length} for minimum #{opt_index})")
|
123
|
+
end
|
124
|
+
|
125
|
+
args.each_with_index do |value, key|
|
126
|
+
unless signature[key]
|
127
|
+
return ArgumentError.new("argument #{key} does not exist in prototype")
|
128
|
+
end
|
129
|
+
|
130
|
+
if !signature[key].nil?
|
131
|
+
ret = check_type(signature[key], value, key)
|
132
|
+
return ret unless ret.nil?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
return args
|
137
|
+
end
|
138
|
+
|
139
|
+
# internal, do not use directly.
|
140
|
+
#
|
141
|
+
# Used to check the sanity of parameterized (named) method calls.
|
142
|
+
def self.validate_params(signature, *args)
|
143
|
+
args = args[0]
|
144
|
+
|
145
|
+
unless args.kind_of?(Hash)
|
146
|
+
return ArgumentError.new("this method takes a hash")
|
147
|
+
end
|
148
|
+
|
149
|
+
args.each do |key, value|
|
150
|
+
unless signature.has_key?(key)
|
151
|
+
return ArgumentError.new("argument '#{key}' does not exist in prototype")
|
152
|
+
end
|
153
|
+
|
154
|
+
if !signature[key].nil?
|
155
|
+
ret = check_type(signature[key], value, key)
|
156
|
+
return ret unless ret.nil?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
keys = signature.each_key.select { |key| [signature[key]].flatten.include?(:required) and !args.has_key?(key) }
|
161
|
+
|
162
|
+
if keys.length > 0
|
163
|
+
return ArgumentError.new("argument(s) '#{keys.sort_by { |x| x.to_s }.join(", ")}' were not found but are required by the prototype")
|
164
|
+
end
|
165
|
+
|
166
|
+
return args
|
167
|
+
end
|
168
|
+
|
169
|
+
# Builds an unbound method as a proc with ordered parameters.
|
170
|
+
#
|
171
|
+
# Example:
|
172
|
+
#
|
173
|
+
# my_proc = build_ordered(String, [Integer, :optional]) do |params|
|
174
|
+
# str, int = params
|
175
|
+
# puts "I received #{str} as a String and #{int} as an Integer!"
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# my_proc.call("foo", 1)
|
179
|
+
#
|
180
|
+
# As explained above, an array to combine multiple parameters at a position
|
181
|
+
# may be used to flag it with additional data. At this time, these
|
182
|
+
# parameters are supported:
|
183
|
+
#
|
184
|
+
# * :optional - is not required as a part of the argument list.
|
185
|
+
#
|
186
|
+
def build_ordered(*args, &block)
|
187
|
+
signature = args
|
188
|
+
|
189
|
+
op_index = signature.index(:optional)
|
190
|
+
|
191
|
+
if op_index and signature.reject { |x| x == :optional }.length != op_index
|
192
|
+
raise ArgumentError, ":optional parameters must be at the end"
|
193
|
+
end
|
194
|
+
|
195
|
+
proc do |*args|
|
196
|
+
params = MethLab.validate_array_params(signature, args)
|
197
|
+
raise params if params.kind_of?(Exception)
|
198
|
+
block.call(params)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# similar to MethLab#build_ordered, but takes a method name as the first
|
203
|
+
# argument that binds to a method with the same name in the current class
|
204
|
+
# or module. Currently cannot be a class method.
|
205
|
+
def def_ordered(method_name, *args, &block)
|
206
|
+
self.send(:define_method, method_name, &build_ordered(*args, &block))
|
207
|
+
method_name
|
208
|
+
end
|
209
|
+
|
210
|
+
# Builds an unbound method as a proc with named (Hash) parameters.
|
211
|
+
#
|
212
|
+
# Example:
|
213
|
+
#
|
214
|
+
# my_proc = build_named(:foo => String, :bar => [Integer, :required]) do |params|
|
215
|
+
# puts "I received #{params[:foo]} as a String and #{params[:bar]} as an Integer!"
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# my_proc.call(:foo => "foo", :bar => 1)
|
219
|
+
#
|
220
|
+
# As explained above, an array to combine multiple parameters at a position
|
221
|
+
# may be used to flag it with additional data. At this time, these
|
222
|
+
# parameters are supported:
|
223
|
+
#
|
224
|
+
# * :required - this field is a required argument (parameters are default optional)
|
225
|
+
#
|
226
|
+
def build_named(*args, &block)
|
227
|
+
signature = args[0]
|
228
|
+
|
229
|
+
proc do |*args|
|
230
|
+
params = MethLab.validate_params(signature, *args)
|
231
|
+
raise params if params.kind_of?(Exception)
|
232
|
+
block.call(params)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# similar to MethLab#build_named, but takes a method name as the first
|
237
|
+
# argument that binds to a method with the same name in the current class
|
238
|
+
# or module. Currently cannot be a class method.
|
239
|
+
def def_named(method_name, *args, &block)
|
240
|
+
self.send(:define_method, method_name, &build_named(*args, &block))
|
241
|
+
method_name
|
242
|
+
end
|
243
|
+
|
244
|
+
if $METHLAB_AUTOINTEGRATE
|
245
|
+
integrate
|
246
|
+
end
|
247
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: methlab
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erik Hollensbe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-27 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: erik@hollensbe.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/methlab.rb
|
26
|
+
has_rdoc: true
|
27
|
+
homepage: http://github.com/erikh/methlab
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: "0"
|
38
|
+
version:
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
requirements: []
|
46
|
+
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 1.3.1
|
49
|
+
signing_key:
|
50
|
+
specification_version: 2
|
51
|
+
summary: A method construction and validation toolkit.
|
52
|
+
test_files: []
|
53
|
+
|