quincunx 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ /.bundle
2
+ /log/*.log
3
+ /tmp
4
+ /tags
5
+ /coverage
6
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'json', '~> 1.7.7'
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ quincunx (0.0.1)
5
+ optional
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.4)
11
+ json (1.7.7)
12
+ multi_json (1.7.3)
13
+ optional (0.0.5)
14
+ rspec (2.13.0)
15
+ rspec-core (~> 2.13.0)
16
+ rspec-expectations (~> 2.13.0)
17
+ rspec-mocks (~> 2.13.0)
18
+ rspec-core (2.13.1)
19
+ rspec-expectations (2.13.0)
20
+ diff-lcs (>= 1.1.3, < 2.0)
21
+ rspec-mocks (2.13.1)
22
+ simplecov (0.7.1)
23
+ multi_json (~> 1.0)
24
+ simplecov-html (~> 0.7.1)
25
+ simplecov-html (0.7.1)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ json (~> 1.7.7)
32
+ quincunx!
33
+ rspec
34
+ simplecov
@@ -0,0 +1,14 @@
1
+ require 'delegate'
2
+ require 'optional'
3
+ require_relative 'quincunx/all'
4
+
5
+ module Quincunx
6
+ Anything = Object
7
+ def self.included(base)
8
+ base.extend(PatternMatcher)
9
+ end
10
+
11
+ class NoMatchForPatternError < StandardError
12
+ end
13
+
14
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'dictionary'
2
+ require_relative 'case_list'
3
+ require_relative 'environment'
4
+ require_relative 'method'
5
+ require_relative 'extractor'
6
+ require_relative 'builder'
7
+ require_relative 'pattern_matcher'
@@ -0,0 +1,25 @@
1
+ module Quincunx
2
+
3
+ class Builder
4
+
5
+ def initialize(args, matchers)
6
+ @args = args
7
+ @matchers = matchers
8
+ end
9
+
10
+ def params
11
+ args.zip(extractors).reduce({}) do |acc, (v, extractor)|
12
+ acc.merge(Extractor.new(extractor, v).extract)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def extractors
19
+ matchers.map { |m| m[1] || {} }
20
+ end
21
+
22
+ attr_reader :args, :matchers
23
+
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ module Quincunx
2
+ class CaseList
3
+
4
+ def << method
5
+ cases << method ; self
6
+ end
7
+
8
+ def match(obj, args)
9
+ Option[cases.map { |method| method.application(obj, args) }.find(&:matches?)]
10
+ end
11
+
12
+ private
13
+
14
+ def cases
15
+ @cases ||= []
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,14 @@
1
+ module Quincunx
2
+ class Dictionary
3
+
4
+ def [](name)
5
+ methods[name] ||= CaseList.new
6
+ methods[name]
7
+ end
8
+
9
+ def methods
10
+ @methods ||= {}
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Quincunx
2
+ class Environment < SimpleDelegator
3
+
4
+ def initialize(obj, params)
5
+ super(obj)
6
+ @params = params
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ params.fetch(name.to_sym) { super }
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :params
16
+
17
+ def self.factory(obj, args, matchers)
18
+ new obj, Builder.new(args, matchers).params
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ module Quincunx
2
+
3
+ class Extractor
4
+
5
+ def initialize(matcher, value)
6
+ @matcher = matcher
7
+ @value = value
8
+ end
9
+
10
+ def extract
11
+ as ? values.merge(as.to_sym => value) : values
12
+ end
13
+
14
+ def values
15
+ case
16
+ keys
17
+ when Array
18
+ keys.reduce({}) { |acc, k| acc.merge(k => value.send(k)) }
19
+ when Hash
20
+ keys.reduce({}) { |acc, (k, v)| acc.merge(v.to_sym => value.send(k)) }
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def keys
27
+ matcher.fetch(:keys, [])
28
+ end
29
+
30
+ def as
31
+ matcher.fetch(:as, false)
32
+ end
33
+
34
+ attr_reader :matcher, :value
35
+
36
+ end
37
+ end
38
+
@@ -0,0 +1,69 @@
1
+ module Quincunx
2
+ class Method
3
+
4
+ def initialize(params, body)
5
+ @params = params
6
+ @body = body
7
+ end
8
+
9
+ attr_reader :params, :body
10
+
11
+ def application(obj, args)
12
+ Application.new(obj, args, params, body)
13
+ end
14
+
15
+ private
16
+
17
+ Application = Struct.new(:obj, :args, :params, :body) do
18
+
19
+ def call
20
+ env.instance_exec(&body)
21
+ end
22
+
23
+ def matches?
24
+ correct_arg_count? && args_match? && contains_right_keys? && passes_guard?
25
+ end
26
+
27
+ def env
28
+ @env ||= Environment.factory(obj, args, matchers)
29
+ end
30
+
31
+ def correct_arg_count?
32
+ matchers.count == args.count
33
+ end
34
+
35
+ def args_match?
36
+ pairs.all? { |(a,b)| a.first === b }
37
+ end
38
+
39
+ def contains_right_keys?
40
+ pairs.all? do |(a,b)|
41
+ (a[1] || {}).fetch(:keys, []).all? { |k| b.respond_to? k }
42
+ end
43
+ end
44
+
45
+ def passes_guard?
46
+ env.instance_exec(&guard)
47
+ end
48
+
49
+ def pairs
50
+ matchers.zip(args)
51
+ end
52
+
53
+ def guard
54
+ params.last.is_a?(Hash) ? params.last.fetch(:when) : ->{ true }
55
+ end
56
+
57
+ def is_guard?(param)
58
+ param.is_a?(Hash) && param.keys == [:when]
59
+ end
60
+
61
+ def matchers
62
+ @matchers ||= (is_guard?(params.last) ? params[0..-2] : params).map do |p|
63
+ p.is_a?(Array) ? p : [p]
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ module Quincunx
2
+
3
+ module PatternMatcher
4
+
5
+ def define name, *args, &body
6
+ cases = dictionary[name] << Method.new(args, body)
7
+ define_method name do |*args, &block|
8
+ cases.match(self, args).match do |m|
9
+ m.some { |body| body.call }
10
+ m.none { raise NoMatchForPatternError }
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def dictionary
18
+ @dictionary ||= Dictionary.new
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'quincunx'
3
+ s.version = '0.0.1'
4
+ s.date = '2013-04-22'
5
+ s.summary = "Pattern matching for method dispatch - in Ruby?!"
6
+ s.description = "This probably shouldn't work..."
7
+ s.authors = ["Russell Dunphy"]
8
+ s.email = ['russell@russelldunphy.com']
9
+ s.files = `git ls-files`.split($\)
10
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
11
+ s.require_paths = ["lib"]
12
+ s.homepage = 'http://github.com/rsslldnphy/quincunx'
13
+
14
+ s.add_dependency "optional"
15
+ s.add_development_dependency "rspec"
16
+ s.add_development_dependency "simplecov"
17
+
18
+ end
19
+
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ class Drink
4
+ end
5
+
6
+ class SoftDrink < Drink
7
+ end
8
+
9
+ class Alcopop < Drink
10
+ end
11
+
12
+ class Bar
13
+ include Quincunx
14
+
15
+ define :serve_irresponsibly, Person, Anything do
16
+ "here you go!"
17
+ end
18
+
19
+ define :serve, [Person, keys: [:name]], SoftDrink do
20
+ "A soft drink? No problem, #{name}"
21
+ end
22
+
23
+ define :serve, [Person, keys: [:name, :age]], Alcopop, when: ->{ age < 18 } do
24
+ "No chance, #{name}, you're only #{age}!"
25
+ end
26
+
27
+ define :serve,
28
+ [Person, keys: [:name, :age]],
29
+ [Alcopop, as: :drink],
30
+ when: -> { age >= 18 } do
31
+
32
+ "Here you go, #{name}, here's your #{drink.class}"
33
+ end
34
+ end
35
+
36
+ describe Bar do
37
+
38
+ let (:bar) { Bar.new }
39
+ let (:child) { Person.new("Russell", 17) }
40
+ let (:adult) { Person.new("Jeremy Irons", 44) }
41
+ let (:hooch) { Alcopop.new }
42
+ let (:ribena) { SoftDrink.new }
43
+
44
+ describe "#serve_irresponsibly" do
45
+ it 'serves anyone anything' do
46
+ bar.serve_irresponsibly(child, hooch).should eq "here you go!"
47
+ end
48
+ end
49
+
50
+ describe "#serve" do
51
+ it 'will serve anyone a soft drink' do
52
+ bar.serve(child, ribena).should eq "A soft drink? No problem, Russell"
53
+ end
54
+
55
+ it 'will not serve children alcopops' do
56
+ bar.serve(child, hooch).should eq "No chance, Russell, you're only 17!"
57
+ end
58
+
59
+ it 'will serve adults alcopops' do
60
+ bar.serve(adult, hooch).should eq "Here you go, Jeremy Irons, here's your Alcopop"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe Builder do
5
+
6
+ let (:russell) { Cat.new("Russell", :tabby) }
7
+ let (:griselda) { Cat.new("Griselda", :persian) }
8
+ let (:args) { [russell, griselda] }
9
+
10
+ let (:matchers) {[
11
+ [Cat, keys: [:type], as: :cat],
12
+ [Cat, keys: [:name]]
13
+ ]}
14
+
15
+ let (:builder) { Builder.new(args, matchers) }
16
+
17
+ it 'builds a hash of the passed params and extracted keys' do
18
+ builder.params.should eq ({
19
+ cat: russell,
20
+ name: "Griselda",
21
+ type: :tabby
22
+ })
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe CaseList do
5
+
6
+ let (:obj) { stub }
7
+ let (:case_list) { CaseList.new }
8
+
9
+ it 'allows methods to be shovelled in and retrieved' do
10
+ method = Method.new([[:foo]], ->{ :bar })
11
+ case_list << method
12
+ case_list.match(obj, [:foo]).map(&:call).should eq Some[:bar]
13
+ end
14
+
15
+ it 'returns a none when asked for a match if there is no match' do
16
+ method = Method.new([[:foo]], ->{ :bar })
17
+ case_list << method
18
+ case_list.match(obj, [:bar]).map(&:call).should be_none
19
+ end
20
+
21
+ it 'returns the correct method if there are multiple ones' do
22
+ first = Method.new([[:foo]], ->{ :bar })
23
+ second = Method.new([[:baz]], ->{ :quux })
24
+ case_list << first << second
25
+ case_list.match(obj, [:foo]).map(&:call).should eq Some[:bar]
26
+ case_list.match(obj, [:baz]).map(&:call).should eq Some[:quux]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe Dictionary do
5
+
6
+ let (:dictionary) { Dictionary.new }
7
+
8
+ it 'adds methods to the dictionary' do
9
+ dictionary[:foo] << Method.new(:bar, :baz)
10
+ dictionary[:foo].should be_a CaseList
11
+ end
12
+
13
+ it 'defaults case lists to empty' do
14
+ dictionary[:quux].should be_a CaseList
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe Environment do
5
+
6
+ Cat = Struct.new(:name, :type)
7
+
8
+ let (:cat) { Cat.new("Russell", "Tabby") }
9
+ let (:params) {{ name: "Felix", paws: 3 }}
10
+
11
+ let (:delegator) { Environment.new(cat, params) }
12
+
13
+ it 'should delegate methods to the original object' do
14
+ delegator.type.should eq "Tabby"
15
+ end
16
+
17
+ it 'should prefer values in the params hash' do
18
+ delegator.name.should eq "Felix"
19
+ end
20
+
21
+ it 'should return values from the params' do
22
+ delegator.paws.should eq 3
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe Extractor do
5
+
6
+ let (:cat) { Cat.new("Russell", "Tabby") }
7
+
8
+ let (:extractor) { Extractor.new(matcher, cat) }
9
+
10
+ subject { extractor.extract }
11
+
12
+ context "given an array of keys" do
13
+ let (:matcher) {{ keys: [:name, :type] }}
14
+
15
+ it "extracts the keys specified in the matcher" do
16
+ subject.should eq name: "Russell", type: "Tabby"
17
+ end
18
+ end
19
+
20
+ context "given a hash of keys to names" do
21
+ let (:matcher) {{ keys: { name: 'cat' } }}
22
+
23
+ it "extracts the keys and adds them to hash using the specified name" do
24
+ subject.should eq cat: "Russell"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module Quincunx
4
+ describe Method do
5
+
6
+ let (:method) { Method.new([[:cat], [:dog]], ->{ :bar }) }
7
+ let (:env) { stub }
8
+
9
+ it 'can be compared to an array of args' do
10
+ method.application(env, [:cat, :dog]).matches?
11
+ end
12
+
13
+ it 'should not match a different array' do
14
+ method.application(env, [:cat, :cat])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Quincunx do
4
+
5
+ let (:test_class) {
6
+ Class.new do
7
+ include Quincunx
8
+
9
+ define :speak, [:cat] do
10
+ :miaow
11
+ end
12
+
13
+ define :speak, [:dog] do
14
+ :woof
15
+ end
16
+
17
+ define :speak, [Person] do
18
+ :hello
19
+ end
20
+
21
+ define :introduce, [Person, as: :person] do
22
+ "hi, i'm #{person.name}"
23
+ end
24
+
25
+ end
26
+ }
27
+
28
+ subject { test_class.new }
29
+
30
+ describe "#define" do
31
+ it { should respond_to :speak }
32
+
33
+ it "should return the thing from the dictionary" do
34
+ subject.speak(:cat).should eq :miaow
35
+ subject.speak(:dog).should eq :woof
36
+ end
37
+
38
+ it "can match on class" do
39
+ russell = Person.new("russell", 29)
40
+ subject.speak(russell).should eq :hello
41
+ end
42
+
43
+ it "doesn't match if the number of args is wrong" do
44
+ expect { subject.speak(:cat, :dog) }.to raise_error Quincunx::NoMatchForPatternError
45
+ end
46
+
47
+ it "binds the arg to a variable if the 'as' keyword is used" do
48
+ russell = Person.new("russell", 29)
49
+ subject.introduce(russell).should eq "hi, i'm russell"
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,30 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/spec'
4
+ end
5
+
6
+ require 'rspec'
7
+ require 'quincunx'
8
+
9
+ RSpec.configure do |config|
10
+ config.order = :rand
11
+ config.color_enabled = true
12
+ end
13
+
14
+ class Cat
15
+ def initialize(name, type)
16
+ @name = name
17
+ @type = type
18
+ end
19
+
20
+ attr_reader :name, :type
21
+ end
22
+
23
+ class Person
24
+ def initialize(name, age)
25
+ @name = name
26
+ @age = age
27
+ end
28
+
29
+ attr_reader :name, :age
30
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quincunx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Russell Dunphy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: optional
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: simplecov
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: This probably shouldn't work...
63
+ email:
64
+ - russell@russelldunphy.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - Gemfile.lock
72
+ - lib/quincunx.rb
73
+ - lib/quincunx/all.rb
74
+ - lib/quincunx/builder.rb
75
+ - lib/quincunx/case_list.rb
76
+ - lib/quincunx/dictionary.rb
77
+ - lib/quincunx/environment.rb
78
+ - lib/quincunx/extractor.rb
79
+ - lib/quincunx/method.rb
80
+ - lib/quincunx/pattern_matcher.rb
81
+ - quincunx.gemspec
82
+ - spec/lib/guard_spec.rb
83
+ - spec/lib/quincunx/builder_spec.rb
84
+ - spec/lib/quincunx/case_list_spec.rb
85
+ - spec/lib/quincunx/dictionary_spec.rb
86
+ - spec/lib/quincunx/environment_spec.rb
87
+ - spec/lib/quincunx/extractor_spec.rb
88
+ - spec/lib/quincunx/method_spec.rb
89
+ - spec/lib/quincunx_spec.rb
90
+ - spec/spec_helper.rb
91
+ homepage: http://github.com/rsslldnphy/quincunx
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.25
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Pattern matching for method dispatch - in Ruby?!
115
+ test_files:
116
+ - spec/lib/guard_spec.rb
117
+ - spec/lib/quincunx/builder_spec.rb
118
+ - spec/lib/quincunx/case_list_spec.rb
119
+ - spec/lib/quincunx/dictionary_spec.rb
120
+ - spec/lib/quincunx/environment_spec.rb
121
+ - spec/lib/quincunx/extractor_spec.rb
122
+ - spec/lib/quincunx/method_spec.rb
123
+ - spec/lib/quincunx_spec.rb
124
+ - spec/spec_helper.rb
125
+ has_rdoc: