match-maker 0.0.1
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/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +1 -0
- data/lib/matchmaker.rb +34 -0
- data/lib/matchmaker/class_matcher.rb +56 -0
- data/lib/matchmaker/default_matcher.rb +17 -0
- data/lib/matchmaker/enum.rb +2 -0
- data/lib/matchmaker/enum/enum_cons.rb +54 -0
- data/lib/matchmaker/enum/enum_matcher.rb +40 -0
- data/lib/matchmaker/evaluator.rb +13 -0
- data/lib/matchmaker/matcher.rb +38 -0
- data/lib/matchmaker/version.rb +3 -0
- data/matchmaker.gemspec +19 -0
- data/spec/matchmaker_spec.rb +269 -0
- data/spec/spec_helper.rb +5 -0
- metadata +64 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 Simão Mata
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Matchmaker
|
|
2
|
+
|
|
3
|
+
Simple pattern matching library for ruby.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
### Simple match by class name
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require 'matchmaker'
|
|
11
|
+
|
|
12
|
+
class Dummy
|
|
13
|
+
def initialize(x, y, z)
|
|
14
|
+
@x, @y, @z = 1, 2, 3
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sum
|
|
18
|
+
@x + @y + @z
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
obj = Dummy.new(1, 2, 3)
|
|
23
|
+
|
|
24
|
+
Matchmaker.match(obj) do
|
|
25
|
+
pattern Dummy do puts "Dummy class matched" end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# => Dummy class matched
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Matching by implemented methods and instance variables
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
Matchmaker.match(obj) do
|
|
36
|
+
pattern Dummy, :w do puts "obj does not have w" end
|
|
37
|
+
pattern Dummy, :sum do puts "But it has a sum: #{sum}" end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# => But it has a sum: 6
|
|
41
|
+
|
|
42
|
+
# You don't even need to specify a class
|
|
43
|
+
Matchmaker.match(obj) do
|
|
44
|
+
pattern :w do puts "obj does not have w" end
|
|
45
|
+
pattern :sum do puts "But it has a sum" end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# => But it has a sum
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### You can access instance variables and methods declared in the pattern
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
Matchmaker.match(obj) do
|
|
56
|
+
pattern Dummy, :x, :sum do puts "x is #{x}, sum is #{sum}" end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# => x is 1, sum is 6
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### You can use `__` as a 'catch all' pattern
|
|
63
|
+
```ruby
|
|
64
|
+
Matchmaker.match(obj) do
|
|
65
|
+
pattern Array do puts "obj is an array" end
|
|
66
|
+
__ do puts "Default matcher matched" end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# => Default matcher matched
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Match a nil value with nil
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
Matchmaker.match(nil) do
|
|
76
|
+
pattern Dummy do puts "this does not match" end
|
|
77
|
+
pattern nil do puts "I AM NIL!!!" end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# => I AM NIL!!!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Gets even more interesting when using Array matchers
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
array_obj = [1, 2, 3]
|
|
87
|
+
|
|
88
|
+
Matchmaker.match(array_obj) do
|
|
89
|
+
enum :x do puts "Does not match because array_objs.size > 1" end
|
|
90
|
+
enum :x, :y, :z, :a do puts "does not match because it's too small" end
|
|
91
|
+
enum :x, :y, :z do puts "You can access x: #{x}, y: #{y} and #{z}" end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# => You can access x: 1, y: 2 and 3
|
|
95
|
+
|
|
96
|
+
Matchmaker.match([]) do
|
|
97
|
+
enum nil do puts "[] or nil matches any empty enum" end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# => [] or nil matches any empty enum
|
|
101
|
+
|
|
102
|
+
# You can use the `enum_cons` matcher to split the enum
|
|
103
|
+
Matchmaker.match(array_obj) do
|
|
104
|
+
enum_cons :x, :xs do puts "You can access head #{x} and tail #{xs} of the enum" end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Matchmaker.match(array_obj) do
|
|
108
|
+
enum_cons :x, :y, :xs do puts "More than one element before tail: #{y}" end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
Matchmaker.match([1]) do
|
|
112
|
+
enum_cons :x, :xs do puts "The tail can be empty: x: #{x}, xs: #{xs}" end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# => The tail can be empty: x: 1, xs: []
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Hashes are kind of supported ;)
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
hash_obj = {a: 1, b: 2}
|
|
122
|
+
|
|
123
|
+
Matchmaker.match(hash_obj) do
|
|
124
|
+
enum_cons :x, :y, :xs do puts "head: #{x}, y: #{y}, tail: #{xs}" end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# => head: [:a, 1], y: [:b, 2], tail: []
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Installation
|
|
132
|
+
|
|
133
|
+
Add this line to your application's Gemfile:
|
|
134
|
+
|
|
135
|
+
gem 'matchmaker'
|
|
136
|
+
|
|
137
|
+
And then execute:
|
|
138
|
+
|
|
139
|
+
$ bundle
|
|
140
|
+
|
|
141
|
+
Or install it yourself as:
|
|
142
|
+
|
|
143
|
+
$ gem install matchmaker
|
|
144
|
+
|
|
145
|
+
## Usage
|
|
146
|
+
|
|
147
|
+
See examples.
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
1. Fork it
|
|
152
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
153
|
+
3. Have fun writing some code
|
|
154
|
+
4. Remember to run tests `ruby matchmaker_spec.rb`
|
|
155
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
|
156
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
|
157
|
+
7. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/matchmaker.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'matchmaker/version'
|
|
4
|
+
require 'matchmaker/matcher'
|
|
5
|
+
|
|
6
|
+
require 'matchmaker/evaluator'
|
|
7
|
+
require 'matchmaker/class_matcher'
|
|
8
|
+
require 'matchmaker/default_matcher'
|
|
9
|
+
require 'matchmaker/enum'
|
|
10
|
+
|
|
11
|
+
module Matchmaker
|
|
12
|
+
class NoMatchError < RuntimeError; end
|
|
13
|
+
|
|
14
|
+
def self.matchers
|
|
15
|
+
{
|
|
16
|
+
:__ => DefaultMatcher,
|
|
17
|
+
:pattern => ClassMatcher,
|
|
18
|
+
:enum_cons => Enum::EnumConsMatcher,
|
|
19
|
+
:enum => Enum::EnumMatcher
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.default_matcher(subject)
|
|
24
|
+
Matcher.new(subject, matchers)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.match(subject, &block)
|
|
28
|
+
default_matcher(subject).match(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
alias_method :marry, :match
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Matchmaker
|
|
2
|
+
class ClassMatcherBindings
|
|
3
|
+
attr_reader :subject, :names
|
|
4
|
+
|
|
5
|
+
def initialize(subject, names)
|
|
6
|
+
@subject, @names = subject, names
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def each_name(&block)
|
|
10
|
+
@names.each(&block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(name)
|
|
14
|
+
if subject.instance_variable_defined?("@#{name}")
|
|
15
|
+
subject.instance_variable_get("@#{name}")
|
|
16
|
+
elsif subject.respond_to?(name)
|
|
17
|
+
subject.send(name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ClassMatcher
|
|
23
|
+
def initialize(subject)
|
|
24
|
+
@subject = subject
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def subject_matches?(*args)
|
|
28
|
+
if args == [nil]
|
|
29
|
+
klass = NilClass
|
|
30
|
+
vars = []
|
|
31
|
+
elsif !args.first.is_a?(Class)
|
|
32
|
+
klass = Object
|
|
33
|
+
vars = args
|
|
34
|
+
else
|
|
35
|
+
klass = args.first
|
|
36
|
+
vars = args[1, args.size]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
return false unless @subject.is_a?(klass)
|
|
40
|
+
|
|
41
|
+
vars.all? do |v|
|
|
42
|
+
@subject.instance_variable_defined?("@#{v}") || @subject.respond_to?(v)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def eval(*args, block)
|
|
47
|
+
vars = args.compact
|
|
48
|
+
vars = args[1, args.size] if args.first.is_a?(Class)
|
|
49
|
+
|
|
50
|
+
bindings = ClassMatcherBindings.new(@subject, vars)
|
|
51
|
+
|
|
52
|
+
Evaluator.new.run_block(bindings, block)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Matchmaker
|
|
2
|
+
class EmptyBindings
|
|
3
|
+
def each_name; end
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class DefaultMatcher
|
|
7
|
+
def initialize(*args); end
|
|
8
|
+
|
|
9
|
+
def subject_matches?(*args)
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def eval(*args, block)
|
|
14
|
+
Evaluator.new.run_block(EmptyBindings.new, block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Matchmaker
|
|
2
|
+
module Enum
|
|
3
|
+
class EnumBindings
|
|
4
|
+
def initialize(enum, names)
|
|
5
|
+
@bindings = build_bindings(enum, names)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def build_bindings(enum, names)
|
|
9
|
+
return {} if names == [nil] || names == [[]] || names == [{}]
|
|
10
|
+
enum = enum.dup
|
|
11
|
+
res = {}
|
|
12
|
+
|
|
13
|
+
names.each_with_index do |name, i|
|
|
14
|
+
e = enum.shift
|
|
15
|
+
|
|
16
|
+
if e.nil?
|
|
17
|
+
res[name] = []
|
|
18
|
+
break
|
|
19
|
+
elsif i == names.size - 1
|
|
20
|
+
res[name] = enum.unshift(e)
|
|
21
|
+
else
|
|
22
|
+
res[name] = e
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
res
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def each_name(&block)
|
|
30
|
+
@bindings.keys.each(&block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get(name)
|
|
34
|
+
@bindings[name]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class EnumConsMatcher
|
|
39
|
+
def initialize(subject)
|
|
40
|
+
@subject = subject
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def subject_matches?(*args)
|
|
44
|
+
return false unless @subject.is_a?(Enumerable)
|
|
45
|
+
return true if @subject.empty? && (args == [nil] || args == [[]])
|
|
46
|
+
@subject.size >= (args.size - 1)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def eval(*args, block)
|
|
50
|
+
Evaluator.new.run_block(EnumBindings.new(@subject, args), block)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Matchmaker
|
|
2
|
+
module Enum
|
|
3
|
+
class HashBindings
|
|
4
|
+
def initialize(enum, bindings)
|
|
5
|
+
@bindings = bindings
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def each_name(&block)
|
|
9
|
+
@bindings.keys.each(&block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get(name)
|
|
13
|
+
@bindings[name]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class EnumMatcher
|
|
18
|
+
def initialize(subject)
|
|
19
|
+
@subject = subject
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def subject_matches?(*args)
|
|
23
|
+
return false unless @subject.is_a?(Enumerable)
|
|
24
|
+
return true if @subject.empty? && (args == [nil] || args == [[]])
|
|
25
|
+
@subject.size == args.size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def eval(*args, block)
|
|
29
|
+
if args == [nil] || args == [[]]
|
|
30
|
+
hash_bindings = {}
|
|
31
|
+
else
|
|
32
|
+
hash_bindings = Hash[args.zip(@subject)]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
bindings = HashBindings.new(@subject, hash_bindings)
|
|
36
|
+
Evaluator.new.run_block(bindings, block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Matchmaker
|
|
2
|
+
class Matcher
|
|
3
|
+
attr_accessor :subject
|
|
4
|
+
|
|
5
|
+
def initialize(subject, matchers)
|
|
6
|
+
@subject = subject
|
|
7
|
+
@matched = false
|
|
8
|
+
|
|
9
|
+
setup_matchers(matchers)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def match(&block)
|
|
13
|
+
result = instance_eval(&block)
|
|
14
|
+
raise NoMatchError unless @matched
|
|
15
|
+
result
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call_matcher(klass, args, block)
|
|
19
|
+
return false if @matched
|
|
20
|
+
matcher = klass.new(subject)
|
|
21
|
+
|
|
22
|
+
if matcher.subject_matches?(*args)
|
|
23
|
+
@matched = true
|
|
24
|
+
matcher.eval(*args, block)
|
|
25
|
+
else
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def setup_matchers(matchers)
|
|
31
|
+
matchers.each do |sym, klass|
|
|
32
|
+
define_singleton_method(sym) do |*args, &block|
|
|
33
|
+
call_matcher(klass, args, block)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/matchmaker.gemspec
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'matchmaker/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "match-maker"
|
|
8
|
+
gem.version = Matchmaker::VERSION
|
|
9
|
+
gem.authors = ["Simão Mata"]
|
|
10
|
+
gem.email = ["simao.m@gmail.com"]
|
|
11
|
+
gem.description = %q{Pattern matching for ruby}
|
|
12
|
+
gem.summary = %q{Simple pattern matching library for ruby}
|
|
13
|
+
gem.homepage = "http://github.com/simao/matchmaker"
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
end
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Matchmaker
|
|
4
|
+
class Dummy
|
|
5
|
+
attr_reader :x, :y, :z
|
|
6
|
+
|
|
7
|
+
def initialize(x, y, z)
|
|
8
|
+
@x, @y, @z = x, y, z
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def calculation
|
|
12
|
+
x + y + z
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe ClassMatcher do
|
|
17
|
+
before do
|
|
18
|
+
@obj = Dummy.new(1, 2, 3)
|
|
19
|
+
@class_matcher = ClassMatcher.new(@obj)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#subject_matches?" do
|
|
23
|
+
it "matches if classname is the same" do
|
|
24
|
+
@class_matcher.subject_matches?(Dummy).must_equal true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "does not match if not all bindings are possible" do
|
|
28
|
+
@class_matcher.subject_matches?(Dummy, :undefined).must_equal false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "matches nil without using explicit NilClass" do
|
|
32
|
+
@class_matcher = ClassMatcher.new(nil)
|
|
33
|
+
@class_matcher.subject_matches?(nil).must_equal true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "does not match nil pattern if it's not a NilClass object" do
|
|
37
|
+
@class_matcher = ClassMatcher.new(@obj)
|
|
38
|
+
@class_matcher.subject_matches?(nil).must_equal false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe Matcher do
|
|
44
|
+
describe "#match" do
|
|
45
|
+
before do
|
|
46
|
+
@matcher = Matcher.new(nil, Matchmaker.matchers)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "only runs ONE matching block" do
|
|
50
|
+
probe = 0
|
|
51
|
+
|
|
52
|
+
@matcher.match do
|
|
53
|
+
pattern nil do probe = 1 end
|
|
54
|
+
__ do probe = 2 end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
probe.must_equal 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "runs default block if exists" do
|
|
61
|
+
probe = 0
|
|
62
|
+
|
|
63
|
+
@matcher.match do
|
|
64
|
+
pattern Dummy do probe = 1 end
|
|
65
|
+
__ do probe = 2 end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
probe.must_equal 2
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "raises exception if no pattern could be matched" do
|
|
72
|
+
|
|
73
|
+
proc {
|
|
74
|
+
@matcher.match do
|
|
75
|
+
pattern Dummy do nil end
|
|
76
|
+
end
|
|
77
|
+
}.must_raise NoMatchError
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "returns the value of the applied match" do
|
|
82
|
+
result = @matcher.match do
|
|
83
|
+
__ { 22 }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
result.must_equal 22
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe "#pattern" do
|
|
90
|
+
before do
|
|
91
|
+
@obj = Dummy.new(1, 2, 3)
|
|
92
|
+
@matcher = Matcher.new(@obj, Matchmaker.matchers)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "yields block when matching only class" do
|
|
96
|
+
probe = @matcher.pattern(Dummy) do probe = true end
|
|
97
|
+
probe.must_equal true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "allows access to subject vars from block" do
|
|
101
|
+
x_value = @matcher.pattern(Dummy, :x) do x end
|
|
102
|
+
|
|
103
|
+
x_value.must_equal @obj.x
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "allows access to subject methods" do
|
|
107
|
+
v = @matcher.pattern(Dummy, :calculation) do calculation end
|
|
108
|
+
v.must_equal @obj.calculation
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "matches only methods/instance variables when not using class" do
|
|
112
|
+
v = @matcher.pattern(:x, :y) do x end
|
|
113
|
+
v.must_equal @obj.x
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "#enum" do
|
|
119
|
+
describe "with a non empty enum" do
|
|
120
|
+
before do
|
|
121
|
+
@obj = [1, 2, 3]
|
|
122
|
+
@matcher = Matcher.new(@obj, Matchmaker.matchers)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "matches array" do
|
|
126
|
+
v = @matcher.enum(:x, :y, :z) do [x, y, z] end
|
|
127
|
+
v.must_equal [1, 2, 3]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "does not match if size is not the same" do
|
|
131
|
+
v = @matcher.enum(:x) do true end
|
|
132
|
+
v.must_be_nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "does not match if enum size is not big enough" do
|
|
136
|
+
v = @matcher.enum(:x) do true end
|
|
137
|
+
v.must_be_nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "does not match nil for empty subject" do
|
|
141
|
+
v = @matcher.enum(nil) do true end
|
|
142
|
+
v.must_be_nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "with an empty enum" do
|
|
149
|
+
before do
|
|
150
|
+
@matcher = Matcher.new([], Matchmaker.matchers)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "matches [] for empty subject" do
|
|
154
|
+
v = @matcher.enum([]) do true end
|
|
155
|
+
v.must_equal true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "matches nil for empty subject" do
|
|
159
|
+
v = @matcher.enum(nil) do true end
|
|
160
|
+
v.must_equal true
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe "#enum_cons" do
|
|
166
|
+
describe "with arrays" do
|
|
167
|
+
before do
|
|
168
|
+
@obj = [1, 2, 3]
|
|
169
|
+
@matcher = Matcher.new(@obj, Matchmaker.matchers)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "matches enumerable" do
|
|
173
|
+
v = @matcher.enum_cons do 1 end
|
|
174
|
+
v.must_equal 1
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "assigns head and tail" do
|
|
178
|
+
head = nil
|
|
179
|
+
tail = nil
|
|
180
|
+
|
|
181
|
+
@matcher.enum_cons(:x, :xs) do head = x; tail = xs end
|
|
182
|
+
|
|
183
|
+
head.must_equal 1
|
|
184
|
+
tail.must_equal [2, 3]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "assigns elements first and then tail" do
|
|
188
|
+
head, head_y, tail = nil
|
|
189
|
+
|
|
190
|
+
@matcher.enum_cons(:x, :y, :xs) do head = x; head_y = y; tail = xs end
|
|
191
|
+
|
|
192
|
+
head.must_equal 1
|
|
193
|
+
head_y.must_equal 2
|
|
194
|
+
tail.must_equal [3]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "allows to match an enum using an empty tail" do
|
|
198
|
+
head, head_y, head_z, tail = nil
|
|
199
|
+
|
|
200
|
+
@matcher.enum_cons(:x, :y, :z, :xs) do
|
|
201
|
+
head = x
|
|
202
|
+
head_y = y
|
|
203
|
+
head_z = z
|
|
204
|
+
tail = xs
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
head.must_equal 1
|
|
208
|
+
head_y.must_equal 2
|
|
209
|
+
head_z.must_equal 3
|
|
210
|
+
tail.must_equal []
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it "does not match if not enough elements in enum" do
|
|
214
|
+
v = @matcher.enum_cons(:x, :y, :v, :z, :xs) do true end
|
|
215
|
+
v.must_be_nil
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe "with hashes" do
|
|
221
|
+
before do
|
|
222
|
+
@matcher = Matcher.new({:a => 1, :b => 2}, Matchmaker.matchers)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "can match hashes" do
|
|
226
|
+
x_, y_, z_ = nil
|
|
227
|
+
|
|
228
|
+
@matcher.enum_cons(:x, :y, :z) do x_ = x; y_ = y; z_ = z end
|
|
229
|
+
x_.must_equal([:a, 1])
|
|
230
|
+
y_.must_equal([:b, 2])
|
|
231
|
+
z_.must_equal([])
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe "with empty enums" do
|
|
236
|
+
before do
|
|
237
|
+
@matcher = Matcher.new([], Matchmaker.matchers)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "matches empty hash with matcher" do
|
|
241
|
+
@matcher = Matcher.new({}, Matchmaker.matchers)
|
|
242
|
+
v = @matcher.enum_cons({}) do true end
|
|
243
|
+
v.must_equal true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it "matches empty array with nil matcher" do
|
|
247
|
+
v = @matcher.enum_cons(nil) do true end
|
|
248
|
+
v.must_equal true
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "matches empty array with empty enum instance" do
|
|
252
|
+
v = @matcher.enum_cons([]) do true end
|
|
253
|
+
v.must_equal true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe "#__" do
|
|
259
|
+
before do
|
|
260
|
+
@obj = Dummy.new(1, 2, 3)
|
|
261
|
+
@matcher = Matcher.new(@obj, Matchmaker.matchers)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "always calls associated block" do
|
|
265
|
+
probe = @matcher.__ do true end
|
|
266
|
+
probe.must_equal true
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: match-maker
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Simão Mata
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-12-10 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Pattern matching for ruby
|
|
15
|
+
email:
|
|
16
|
+
- simao.m@gmail.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- .gitignore
|
|
22
|
+
- Gemfile
|
|
23
|
+
- LICENSE.txt
|
|
24
|
+
- README.md
|
|
25
|
+
- Rakefile
|
|
26
|
+
- lib/matchmaker.rb
|
|
27
|
+
- lib/matchmaker/class_matcher.rb
|
|
28
|
+
- lib/matchmaker/default_matcher.rb
|
|
29
|
+
- lib/matchmaker/enum.rb
|
|
30
|
+
- lib/matchmaker/enum/enum_cons.rb
|
|
31
|
+
- lib/matchmaker/enum/enum_matcher.rb
|
|
32
|
+
- lib/matchmaker/evaluator.rb
|
|
33
|
+
- lib/matchmaker/matcher.rb
|
|
34
|
+
- lib/matchmaker/version.rb
|
|
35
|
+
- matchmaker.gemspec
|
|
36
|
+
- spec/matchmaker_spec.rb
|
|
37
|
+
- spec/spec_helper.rb
|
|
38
|
+
homepage: http://github.com/simao/matchmaker
|
|
39
|
+
licenses: []
|
|
40
|
+
post_install_message:
|
|
41
|
+
rdoc_options: []
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
none: false
|
|
46
|
+
requirements:
|
|
47
|
+
- - ! '>='
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ! '>='
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '0'
|
|
56
|
+
requirements: []
|
|
57
|
+
rubyforge_project:
|
|
58
|
+
rubygems_version: 1.8.24
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 3
|
|
61
|
+
summary: Simple pattern matching library for ruby
|
|
62
|
+
test_files:
|
|
63
|
+
- spec/matchmaker_spec.rb
|
|
64
|
+
- spec/spec_helper.rb
|