methlab 0.0.3

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 (2) hide show
  1. data/lib/methlab.rb +247 -0
  2. metadata +53 -0
@@ -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
+