matches 1.0.4 → 1.1.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/Rakefile +5 -2
- data/VERSION +1 -1
- data/features/define_matches.feature +1 -0
- data/lib/match_method.rb +15 -5
- data/lib/matches.rb +64 -9
- data/matches.gemspec +1 -1
- data/spec/matches_spec.rb +55 -16
- metadata +1 -1
data/Rakefile
CHANGED
@@ -52,10 +52,13 @@ Spec::Rake::SpecTask.new(:spec) do |spec|
|
|
52
52
|
end
|
53
53
|
|
54
54
|
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
55
|
-
|
55
|
+
spec.spec_opts = ['--options', "\"spec/spec.opts\""]
|
56
|
+
spec.spec_files = FileList['spec/*_spec.rb']
|
56
57
|
spec.libs << 'lib' << 'spec'
|
57
|
-
spec.pattern = 'spec/*_spec.rb'
|
58
58
|
spec.rcov = true
|
59
|
+
spec.rcov_opts = lambda do
|
60
|
+
IO.readlines("spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
61
|
+
end
|
59
62
|
end
|
60
63
|
|
61
64
|
task :spec => :check_dependencies
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/lib/match_method.rb
CHANGED
@@ -38,19 +38,29 @@ class MatchMethod
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# SWEET HACK ZOMG. Mind has been blown.
|
41
|
-
# http://www.ruby-forum.com/topic/54096
|
42
|
-
# Mauricio Fernandez is a Ruby beast.
|
43
41
|
# This provides instance-exec-like functionality in Ruby 1.8.
|
42
|
+
# Originally saw this here:
|
43
|
+
# http://www.ruby-forum.com/topic/54096
|
44
|
+
# It appears to have been revised here, so this is a new version:
|
45
|
+
# http://eigenclass.org/hiki/bounded+space+instance_exec
|
44
46
|
|
45
47
|
if RUBY_VERSION.split('.')[1].to_i < 9
|
46
48
|
class Object
|
49
|
+
module InstanceExecHelper; end
|
50
|
+
include InstanceExecHelper
|
47
51
|
def instance_exec(*args, &block)
|
48
|
-
|
49
|
-
|
52
|
+
begin
|
53
|
+
old_critical, Thread.critical = Thread.critical, true
|
54
|
+
n = 0
|
55
|
+
n += 1 while respond_to?(mname="__instance_exec#{n}")
|
56
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
57
|
+
ensure
|
58
|
+
Thread.critical = old_critical
|
59
|
+
end
|
50
60
|
begin
|
51
61
|
ret = send(mname, *args)
|
52
62
|
ensure
|
53
|
-
|
63
|
+
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
54
64
|
end
|
55
65
|
ret
|
56
66
|
end
|
data/lib/matches.rb
CHANGED
@@ -2,14 +2,42 @@ require File.dirname(__FILE__) + "/match_method"
|
|
2
2
|
|
3
3
|
# Defines all the necessary components to allow defining dynamic methods.
|
4
4
|
module MatchDef
|
5
|
-
# Defines a new class method that allows you to define
|
5
|
+
# Defines a new class method that allows you to define dynamic instance methods.
|
6
6
|
# Takes a regular expression and a block, which are stored and called later.
|
7
7
|
def matches(regexp, &block)
|
8
|
-
|
8
|
+
# This evil voodoo scares the crud out of me.
|
9
|
+
# It sneaks into the instance upon which we are called
|
10
|
+
# and sets a class variable, so we can keep track of what
|
11
|
+
# match methods have been defined.
|
12
|
+
unless self.send(:class_variable_defined?, :@@match_methods)
|
13
|
+
self.send(:class_variable_set, :@@match_methods, [])
|
14
|
+
self.send(:class_variable_set, :@@cached_match_methods, [])
|
15
|
+
end
|
16
|
+
|
17
|
+
append_to_methods = Proc.new do |method|
|
18
|
+
current = self.send(:class_variable_get, :@@match_methods)
|
19
|
+
self.send(:class_variable_set, :@@match_methods, current + [method])
|
20
|
+
end
|
21
|
+
|
22
|
+
@@available_methods = Proc.new do
|
23
|
+
self.send(:class_variable_get, :@@match_methods)
|
24
|
+
end
|
25
|
+
|
26
|
+
@@append_to_cached_methods = Proc.new do |method|
|
27
|
+
current = self.send(:class_variable_get, :@@cached_match_methods)
|
28
|
+
self.send(:class_variable_set, :@@cached_match_methods, current + [method])
|
29
|
+
end
|
30
|
+
|
31
|
+
# The above statement tweaks the scope, so when we refer to
|
32
|
+
# @@match_methods here, what we're really doing is referring to
|
33
|
+
# the above @@match_methods.
|
34
|
+
self.class_eval do
|
35
|
+
append_to_methods.call(MatchMethod.new( :matcher => regexp,
|
36
|
+
:proc => block ))
|
37
|
+
end
|
9
38
|
|
10
|
-
@@match_methods << MatchMethod.new( :matcher => regexp,
|
11
|
-
:proc => block )
|
12
39
|
self.class_eval {
|
40
|
+
|
13
41
|
unless method_defined? :method_missing
|
14
42
|
def method_missing(meth, *args, &block); super; end
|
15
43
|
end
|
@@ -18,13 +46,28 @@ module MatchDef
|
|
18
46
|
# Defines a +method_missing+ that is aware of the
|
19
47
|
# dynamically-defined methods and will call them if appropriate.
|
20
48
|
def match_method_missing(message, *args, &block)
|
21
|
-
|
22
|
-
|
49
|
+
result = nil
|
50
|
+
|
51
|
+
# Attempt to evaluate this using a MatchMethod
|
52
|
+
matched = @@available_methods.call.find do |mm|
|
23
53
|
if mm.matches?(message)
|
24
|
-
|
54
|
+
|
55
|
+
# Cache the method onto the class
|
56
|
+
self.class.send(:define_method, message, lambda { |*largs|
|
57
|
+
return mm.match(self, message, *largs)
|
58
|
+
})
|
59
|
+
|
60
|
+
# Store the name of the cached method in order to facilitate
|
61
|
+
# introspection.
|
62
|
+
@@append_to_cached_methods.call(message)
|
63
|
+
|
64
|
+
# Call the actual method
|
65
|
+
result = self.send(message, *args)
|
66
|
+
|
67
|
+
true
|
25
68
|
end
|
26
69
|
end
|
27
|
-
return result
|
70
|
+
return matched ? result : old_method_missing(message, args, &block)
|
28
71
|
end
|
29
72
|
|
30
73
|
alias_method :old_method_missing, :method_missing
|
@@ -33,10 +76,22 @@ module MatchDef
|
|
33
76
|
}
|
34
77
|
end
|
35
78
|
|
79
|
+
def reset_match_methods
|
80
|
+
# @@match_methods = []
|
81
|
+
end
|
82
|
+
|
36
83
|
# Allows you to delete all defined dynamic methods on a class.
|
37
84
|
# This permits testing.
|
38
85
|
def reset_match_methods
|
39
|
-
|
86
|
+
self.send(:class_variable_set, :@@match_methods, [])
|
87
|
+
|
88
|
+
if self.send(:class_variable_defined?, :@@cached_match_methods)
|
89
|
+
self.send(:class_variable_get, :@@cached_match_methods).each do |method|
|
90
|
+
self.send(:undef_method, method)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
self.send(:class_variable_set, :@@cached_match_methods, [])
|
40
95
|
end
|
41
96
|
end
|
42
97
|
|
data/matches.gemspec
CHANGED
data/spec/matches_spec.rb
CHANGED
@@ -3,15 +3,9 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe MatchDef do
|
4
4
|
before(:each) do
|
5
5
|
Hippo.reset_match_methods
|
6
|
-
|
6
|
+
|
7
|
+
Object.send(:remove_const, :Hippo)
|
7
8
|
class Hippo
|
8
|
-
def method_missing(message, *args)
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
if method_defined?(:match_method_missing)
|
13
|
-
undef match_method_missing
|
14
|
-
end
|
15
9
|
end
|
16
10
|
end
|
17
11
|
|
@@ -49,6 +43,18 @@ describe MatchDef do
|
|
49
43
|
test.bar_fight()
|
50
44
|
end
|
51
45
|
|
46
|
+
it "should support arguments, too" do
|
47
|
+
Hippo.class_eval do
|
48
|
+
matches /bar_(\w+)/ do |activity, style|
|
49
|
+
worked(activity, style)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
test = Hippo.new
|
54
|
+
test.should_receive(:worked).once.with('fight', 'crazy')
|
55
|
+
test.bar_fight('crazy')
|
56
|
+
end
|
57
|
+
|
52
58
|
it "should fall through normally if no match" do
|
53
59
|
Hippo.class_eval do
|
54
60
|
matches /bar_(\w+)/ do |activity|
|
@@ -85,9 +91,12 @@ describe MatchDef do
|
|
85
91
|
throw "Should never be reached"
|
86
92
|
end
|
87
93
|
end
|
88
|
-
|
94
|
+
|
95
|
+
herman = Hippo.new
|
96
|
+
lambda { herman.second }.should raise_error(NameError)
|
97
|
+
|
89
98
|
test = Rhino.new
|
90
|
-
lambda { test.second
|
99
|
+
lambda { test.second }.should raise_error(NoMethodError)
|
91
100
|
end
|
92
101
|
|
93
102
|
end
|
@@ -109,22 +118,52 @@ describe MatchDef do
|
|
109
118
|
|
110
119
|
it "should differentiate between class and instance methods" do
|
111
120
|
class Hippo
|
112
|
-
matches /
|
113
|
-
|
121
|
+
matches /something/ do
|
122
|
+
throw "Called on instance"
|
114
123
|
end
|
115
124
|
|
116
125
|
class << self
|
117
|
-
matches /
|
118
|
-
|
126
|
+
matches /something/ do
|
127
|
+
throw "Called on class"
|
119
128
|
end
|
120
129
|
end
|
121
130
|
end
|
122
131
|
|
123
132
|
herman = Hippo.new
|
124
|
-
Hippo.should_receive(:failed).never
|
125
|
-
herman.should_receive(:worked).once
|
126
133
|
|
134
|
+
lambda { herman.something }.should raise_error(NameError)
|
135
|
+
lambda { Hippo.something }.should raise_error(NameError)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "caching" do
|
140
|
+
it "should cache methods when they are called" do
|
141
|
+
class Hippo
|
142
|
+
matches /foo/ do
|
143
|
+
worked
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
herman = Hippo.new
|
148
|
+
herman.should_receive(:worked).once
|
149
|
+
|
127
150
|
herman.foo
|
151
|
+
herman.methods.should include('foo')
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should support complex, multi-argument cached methods" do
|
155
|
+
class Hippo
|
156
|
+
matches /foo_(\w+)/ do |a, b|
|
157
|
+
worked(a, b)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
herman = Hippo.new
|
162
|
+
herman.should_receive(:worked).twice.with('bar', 'baz')
|
163
|
+
|
164
|
+
herman.foo_bar('baz')
|
165
|
+
herman.methods.should include('foo_bar')
|
166
|
+
herman.foo_bar('baz')
|
128
167
|
end
|
129
168
|
end
|
130
169
|
end
|