matchete 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3717143d8e222ee9da803cd7647f328c36f56a7
4
- data.tar.gz: bf3af40853ad0382c1776698fb8997da63a7254b
3
+ metadata.gz: d82680012c34cb8d63c1e461711b34727036a455
4
+ data.tar.gz: a23af03a534d39a1295855a959eb362ceebbda82
5
5
  SHA512:
6
- metadata.gz: 4684675527ccb701546d644fb324d189befd55d0f4dfcb77fa09e58dab98b67c40ccd629494e3cdc495b54a3c188d1acd67a38fb2b02ea19443ecca87596e8f8
7
- data.tar.gz: 30b6af0a2dbf30a61cf307cdad0d9e691aa9039b34aa4c6569c90a8876bc937a5975d93d4261286dbbfe4243510f4e1d11883eddec29bf62ea93649e7b3dd583
6
+ metadata.gz: 29d6a30c0274d6fbe59c79dcbbc1c78c5359f76bddc2791c8afeb8980e3219bcc44e6eebe837997e4644497fd9f50dc89f7503130c3488a33757a787f2e9d4bf
7
+ data.tar.gz: 9fae0969b2f2aa630d3608d38db7c91fca1a56f32ca423a0aacfbb11952bf1db45bf025a510faf96694a702e280007cf59468719bbc8138be15f7fa64385b076
data/README.md CHANGED
@@ -1 +1,103 @@
1
- #Matchete
1
+ Matchete
2
+ =========
3
+
4
+ Matchete provides a DSL for method overloading based on pattern matching for Ruby.
5
+
6
+ It's just a quick hack inspired by weissbier and the use-return-values-of-method-definitions DSL technique used in [harmonic](https://github.com/s2gatev/harmonic)
7
+
8
+ Install
9
+ -----
10
+ `gem install matchete`
11
+
12
+ Usage
13
+ -----
14
+
15
+ ```ruby
16
+ require 'matchete'
17
+
18
+ class FactorialStrikesAgain
19
+ include Matchete
20
+
21
+ on 1,
22
+ def factorial(value)
23
+ 1
24
+ end
25
+
26
+ on -> x { x > 1 },
27
+ def factorial(value)
28
+ value * factorial(value - 1)
29
+ end
30
+ end
31
+
32
+ FactorialStrikesAgain.new.factorial(4) #24
33
+ FactorialStrikesAgain.new.factorial(-2) #Matchete::NotResolvedError No matching factorial method for args [-2]
34
+ ```
35
+
36
+ ```ruby
37
+ require 'matchete'
38
+
39
+ class Converter
40
+ include Matchete
41
+
42
+ on Integer,
43
+ def convert(value)
44
+ [:integer, value]
45
+ end
46
+
47
+ on Hash,
48
+ def convert(values)
49
+ [:dict, values.map { |k, v| [convert(k), convert(v)] }]
50
+ end
51
+
52
+ on /reserved_/,
53
+ def convert(value)
54
+ [:reserved_symbol, value]
55
+ end
56
+
57
+ on String,
58
+ def convert(value)
59
+ [:string, value]
60
+ end
61
+
62
+ on ['deleted', [Integer, Any]],
63
+ def convert(value)
64
+ ['deleted', value[1]]
65
+ end
66
+
67
+ on :not_implemented?,
68
+ def convert(value)
69
+ [:fail, value]
70
+ end
71
+
72
+ on free: Integer, method:
73
+ def convert(free:)
74
+ [:rofl, free]
75
+ end
76
+
77
+ default def convert(value)
78
+ [:z, value]
79
+ end
80
+
81
+ def not_implemented?(value)
82
+ value.is_a? Symbol
83
+ end
84
+ end
85
+
86
+ converter = Converter.new
87
+ p converter.convert(2) #[:integer, 2]
88
+ p converter.convert({2 => 4}) #[:dict, [[[:integer, 2], [:integer, 4]]]
89
+ p converter.convert('reserved_l') #[;reserved_symbol, 'l']
90
+ p converter.convert('zaza') #[:string, 'zaza']
91
+ p converter.convert(['deleted', [2, Array]]) #['deleted', [2, Array]]
92
+ p converter.convert(:f) #[:fail, :f]
93
+ p converter.convert(free: 2) #[:rofl, 2]
94
+ p converter.convert(2.2) #[:z, 2.2]
95
+ ```
96
+
97
+ Todo
98
+ -----
99
+
100
+ Copyright
101
+ -----
102
+
103
+ Copyright (c) 2014 Alexander Ivanov. See LICENSE for further details.
data/lib/matchete.rb CHANGED
@@ -1,31 +1,38 @@
1
1
  require 'set'
2
+ require_relative 'matchete/exceptions'
2
3
 
3
4
  module Matchete
4
- class NotResolvedError < StandardError
5
- end
6
-
7
5
  def self.included(klass)
8
6
  klass.extend ClassMethods
9
- klass.instance_variable_set "@functions", {}
10
- klass.instance_variable_set "@default_functions", {}
7
+ klass.instance_variable_set "@methods", {}
8
+ klass.instance_variable_set "@default_methods", {}
11
9
  end
12
10
 
13
11
  Any = -> (x) { true }
14
12
  None = -> (x) { false }
15
13
 
16
14
  module ClassMethods
17
- def on(*guards, function)
18
- @functions[function] ||= []
19
- @functions[function] << [guards, instance_method(function)]
20
- convert_to_matcher function
15
+ def on(*args, **kwargs)
16
+ if kwargs.count.zero?
17
+ *guard_args, method_name = args
18
+ guard_kwargs = {}
19
+ else
20
+ method_name = kwargs[:method]
21
+ kwargs.delete :method
22
+ guard_args = args
23
+ guard_kwargs = kwargs
24
+ end
25
+ @methods[method_name] ||= []
26
+ @methods[method_name] << [guard_args, guard_kwargs, instance_method(method_name)]
27
+ convert_to_matcher method_name
21
28
  end
22
29
 
23
30
  def default(method_name)
24
- @default_functions[method_name] = instance_method(method_name)
31
+ @default_methods[method_name] = instance_method(method_name)
25
32
  convert_to_matcher method_name
26
33
  end
27
34
 
28
- def duck(*method_names)
35
+ def supporting(*method_names)
29
36
  -> object do
30
37
  method_names.all? do |method_name|
31
38
  object.respond_to? method_name
@@ -33,51 +40,72 @@ module Matchete
33
40
  end
34
41
  end
35
42
 
36
- def convert_to_matcher(function)
37
- define_method(function) do |*args|
38
- guards = self.class.instance_variable_get('@functions')[function].find do |guards, _|
39
- self.class.match_guards guards, args
40
- end
41
-
42
- handler = if guards.nil?
43
- default_method = self.class.instance_variable_get('@default_functions')[function]
44
- if default_method
45
- default_method
46
- else
47
- raise NotResolvedError.new("not resolved #{function} with #{args}")
48
- end
49
- else
50
- guards[1]
51
- end
52
-
53
- handler.bind(self).call *args
43
+ def convert_to_matcher(method_name)
44
+ define_method(method_name) do |*args, **kwargs|
45
+ call_overloaded(method_name, args: args, kwargs: kwargs)
54
46
  end
55
47
  end
56
-
57
- def match_guards(guards, args)
58
- guards.zip(args).all? do |guard, arg|
59
- match_guard guard, arg
48
+ end
49
+
50
+ def call_overloaded(method_name, args: [], kwargs: {})
51
+ handler = find_handler(method_name, args, kwargs)
52
+ if handler.parameters.any? do |type, _|
53
+ [:key, :keyrest, :keyreq].include? type
60
54
  end
55
+ handler.bind(self).call *args, **kwargs
56
+ else
57
+ handler.bind(self).call *args
58
+ end
59
+ #insane workaround, because if you have
60
+ #def z(f);end
61
+ #and you call it like that
62
+ #a(2, **{e: 4})
63
+ #it raises wrong number of arguments (2 for 1)
64
+ #clean later
65
+ end
66
+
67
+ def find_handler(method_name, args, kwargs)
68
+ guards = self.class.instance_variable_get('@methods')[method_name].find do |guard_args, guard_kwargs, _|
69
+ match_guards guard_args, guard_kwargs, args, kwargs
61
70
  end
62
71
 
63
- def match_guard(guard, arg)
64
- case guard
65
- when Module
66
- arg.is_a? guard
67
- when Symbol
68
- send guard, arg
69
- when Proc
70
- guard.call arg
71
- when String
72
- guard == arg
73
- when Regexp
74
- arg.is_a? String and guard.match arg
75
- when Array
76
- arg.is_a?(Array) and
77
- guard.zip(arg).all? { |child_guard, child| match_guard child_guard, child }
78
- else
79
- guard == arg
72
+ if guards.nil?
73
+ default_method = self.class.instance_variable_get('@default_methods')[method_name]
74
+ if default_method
75
+ default_method
76
+ else
77
+ raise NotResolvedError.new("No matching #{method_name} method for args #{args}")
80
78
  end
79
+ else
80
+ guards[2]
81
+ end
82
+ end
83
+
84
+ def match_guards(guard_args, guard_kwargs, args, kwargs)
85
+ return false if guard_args.count != args.count || guard_kwargs.count != kwargs.count
86
+ guard_args.zip(args).all? do |guard, arg|
87
+ match_guard guard, arg
88
+ end and
89
+ guard_kwargs.all? do |label, guard|
90
+ match_guard guard, kwargs[label]
91
+ end
92
+ end
93
+
94
+ def match_guard(guard, arg)
95
+ case guard
96
+ when Module
97
+ arg.is_a? guard
98
+ when Symbol
99
+ send guard, arg
100
+ when Proc
101
+ guard.call arg
102
+ when Regexp
103
+ arg.is_a? String and guard.match arg
104
+ when Array
105
+ arg.is_a?(Array) and
106
+ guard.zip(arg).all? { |child_guard, child| match_guard child_guard, child }
107
+ else
108
+ guard == arg
81
109
  end
82
110
  end
83
111
  end
@@ -0,0 +1,7 @@
1
+ module Matchete
2
+ class NotResolvedError < StandardError
3
+ end
4
+ end
5
+
6
+
7
+
data/matchete.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'matchete'
6
- s.version = '0.0.1'
6
+ s.version = '0.0.2'
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Alexander Ivanov"]
9
9
  s.email = ["alehander42@gmail.com"]
@@ -69,4 +69,177 @@ describe Matchete do
69
69
  a.play([2, 2.2]).should eq [:integer, :float]
70
70
  a.play([[2], Matchete]).should eq :s
71
71
  end
72
- end
72
+
73
+ it 'can use a pattern based on exact values' do
74
+ class A
75
+ include Matchete
76
+
77
+ on 2, Integer,
78
+ def play(value, obj)
79
+ 2
80
+ end
81
+
82
+ on 4, Integer,
83
+ def play(value, obj)
84
+ 4
85
+ end
86
+ end
87
+
88
+ a = A.new
89
+ a.play(2, 4).should eq 2
90
+ a.play(4, 4).should eq 4
91
+ -> { a.play(8, 2) }.should raise_error(Matchete::NotResolvedError)
92
+ end
93
+
94
+ it 'can use a pattern based on regexes' do
95
+ class A
96
+ include Matchete
97
+
98
+ on /z/,
99
+ def play(value)
100
+ 'z'
101
+ end
102
+
103
+ on /y/,
104
+ def play(value)
105
+ 'y'
106
+ end
107
+ end
108
+
109
+ a = A.new
110
+ a.play('zewr').should eq 'z'
111
+ a.play('yy').should eq 'y'
112
+ end
113
+
114
+ it 'can use a default method when everything else fails' do
115
+ class A
116
+ include Matchete
117
+
118
+ on Integer,
119
+ def play(value)
120
+ :integer
121
+ end
122
+ end
123
+
124
+ -> { A.new.play(2.2) }.should raise_error(Matchete::NotResolvedError)
125
+
126
+ class A
127
+ default def play(value)
128
+ :else
129
+ end
130
+ end
131
+
132
+ A.new.play(2.2).should eq :else
133
+ end
134
+
135
+ it 'can use a pattern based on existing predicate methods given as symbols' do
136
+ class A
137
+ include Matchete
138
+
139
+ on :even?,
140
+ def play(value)
141
+ value
142
+ end
143
+
144
+ def even?(value)
145
+ value.remainder(2).zero? #so gay and gay
146
+ end
147
+ end
148
+
149
+ A.new.play(2).should eq 2
150
+ -> { A.new.play(5) }.should raise_error(Matchete::NotResolvedError)
151
+ end
152
+
153
+ it 'can use a pattern based on a lambda predicate' do
154
+ class A
155
+ include Matchete
156
+
157
+ on -> x { x % 2 == 0 },
158
+ def play(value)
159
+ value
160
+ end
161
+ end
162
+
163
+ A.new.play(2).should eq 2
164
+ -> { A.new.play(7) }.should raise_error(Matchete::NotResolvedError)
165
+ end
166
+
167
+ it 'can use a pattern based on responding to methods' do
168
+ class A
169
+ include Matchete
170
+
171
+ on supporting(:map),
172
+ def play(value)
173
+ value
174
+ end
175
+ end
176
+
177
+ A.new.play([]).should eq []
178
+ -> { A.new.play(4) }.should raise_error(Matchete::NotResolvedError)
179
+ end
180
+
181
+ it 'can match on different keyword arguments' do
182
+ class A
183
+ include Matchete
184
+
185
+ on e: Integer, f: String, method:
186
+ def play(e:, f:)
187
+ :y
188
+ end
189
+ end
190
+
191
+ A.new.play(e: 0, f: "y").should eq :y
192
+ -> { A.new.play(e: "f", f: Class)}.should raise_error(Matchete::NotResolvedError)
193
+ end
194
+
195
+ it 'can match on multiple different kinds of patterns' do
196
+ class A
197
+ include Matchete
198
+ end
199
+
200
+ A.new.match_guards([Integer, Float], {}, [8, 8.8], {}).should be_true
201
+ end
202
+
203
+ describe '#match_guard' do
204
+ before :all do
205
+ class A
206
+ include Matchete
207
+
208
+ def even?(value)
209
+ value.remainder(2).zero?
210
+ end
211
+ end
212
+ @a = A.new
213
+ end
214
+
215
+ it 'matches modules and classes' do
216
+ @a.match_guard(Integer, 2).should be_true
217
+ @a.match_guard(Class, 4).should be_false
218
+ end
219
+
220
+ it 'matches methods given as symbols' do
221
+ @a.match_guard(:even?, 2).should be_true
222
+ -> { @a.match_guard(:odd?, 4) }.should raise_error
223
+ end
224
+
225
+ it 'matches predicates given as lambdas' do
226
+ @a.match_guard(-> x { x == {} }, {}).should be_true
227
+ end
228
+
229
+ it 'matches on regex' do
230
+ @a.match_guard(/a/, 'aw').should be_true
231
+ @a.match_guard(/z/, 'lol').should be_false
232
+ end
233
+
234
+ it 'matches on nested arrays' do
235
+ @a.match_guard([Integer, [:even?]], [2, [4]]).should be_true
236
+ @a.match_guard([Float, [:even?]], [2.2, [7]]).should be_false
237
+ end
238
+
239
+ it 'matches on exact values' do
240
+ @a.match_guard(2, 2).should be_true
241
+ @a.match_guard('d', 'f').should be_false
242
+ end
243
+ end
244
+ end
245
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matchete
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Ivanov
@@ -36,6 +36,7 @@ files:
36
36
  - README.md
37
37
  - Rakefile
38
38
  - lib/matchete.rb
39
+ - lib/matchete/exceptions.rb
39
40
  - matchete.gemspec
40
41
  - spec/matchete_spec.rb
41
42
  - spec/spec_helper.rb