quincunx 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.
@@ -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: