implements 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.
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ echo -e "\033[0;36mRuby Appraiser: running\033[0m"
3
+ bundle exec ruby-appraiser --all --mode=staged
4
+ result_code=$?
5
+ if [ $result_code -gt "0" ]; then
6
+ echo -en "\033[0;31m" # RED
7
+ echo "[✘] Ruby Appraiser found newly-created defects and "
8
+ echo " has blocked your commit."
9
+ echo " Fix the defects and commit again."
10
+ echo " To bypass, commit again with --no-verify."
11
+ echo -en "\033[0m" # RESET
12
+ exit $result_code
13
+ else
14
+ echo -en "\033[0;32m" # GREEN
15
+ echo "[✔] Ruby Appraiser ok"
16
+ echo -en "\033[0m" #RESET
17
+ fi
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ script: "bundle exec rake spec"
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - jruby-19mode
8
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ gem 'activesupport', require: false
5
+
6
+ # Specify your gem's dependencies in implements.gemspec
7
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ryan Biesemeyer
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.
@@ -0,0 +1,173 @@
1
+ # Implements
2
+
3
+ `Implements` is a tool for building modular libraries and tools as
4
+ interfaces, for implementing those interfaces, and ensuring that
5
+ consumers are able to load the best available implementation at
6
+ runtime.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'implements'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install implements
21
+
22
+ ## Usage
23
+
24
+ `Implements` was created as a dependency of my [redis-copy][] gem, which
25
+ provides multiple implementations of each of multiple interfaces in order to
26
+ provide support for new features in redis, while falling back gracefully
27
+ (sometimes multiple steps) to less-optimal implementations when
28
+ the underlying support is not present.
29
+
30
+ [redis-copy]: https://github.com/yaauie/redis-copy
31
+
32
+ The goal of the `implements` gem in particular is to provide an implementation
33
+ registry that is attached to the interface, and can be used to provide
34
+ the best-possible implementation for a given scenario. It also allows
35
+ third-party libraries to provide their own implementations of an interface
36
+ without having to touch the library that contains their upstream interface.
37
+
38
+ Below you will find a simplified example:
39
+
40
+ ``` ruby
41
+ require 'implements/global'
42
+
43
+ module RedisCopy
44
+ module KeyEmitter
45
+ extend Implements::Interface
46
+
47
+ # @param redis_connection [Object]
48
+ # @return [void]
49
+ def intialize(redis_connection)
50
+ @redis = redis_connection
51
+ end
52
+
53
+ # @param keys [String] - ('*') a glob-ish pattern
54
+ # @return [Enumerable<String>]
55
+ def keys(pattern = '*')
56
+ raise NotImplementedError
57
+ end
58
+
59
+ # ...
60
+
61
+ class Default
62
+ implements KeyEmitter
63
+
64
+ def keys(pattern = '*')
65
+ @redis.keys(pattern)
66
+ end
67
+ end
68
+
69
+ class Scanner
70
+ # note how a block is given to `implements`.
71
+ # this block is called with the class' initialize arguments
72
+ # to determine whether or not this implementation is compatible
73
+ # with the input and its state before initializing the object.
74
+ implements KeyEmitter do |redis_connection|
75
+ bin_version = Gem::Version.new(redis_connection.info['redis_version'])
76
+ bin_requirement = Gem::Requirement.new('>= 2.7.105')
77
+
78
+ break false unless bin_requirement.satisfied_by?(bin_version)
79
+
80
+ redis_connection.respond_to?(:scan_each)
81
+ end
82
+
83
+ def keys(pattern = '*')
84
+ @redis.scan_each(match: pattern, count: 1000)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ ```
90
+
91
+ The consumer of this interface, then, can get the best available implementation,
92
+ given their environment and the object(s) passed to `#initialize`, without
93
+ having to know anything about the implementations themselves:
94
+
95
+ ``` ruby
96
+ source_redis = Redis.new(port: 9736) # a scanner-compatible redis process (>= 2.7.105)
97
+ key_emitter = RedisCopy::KeyEmitter.implementation.new(source_redis)
98
+ # => <RedisCopy::KeyEmitter::Scanner: ... >
99
+ key_emitter.keys('schedule:*').to_enum
100
+ # => <Enumerator ...>
101
+
102
+ source_redis = Redis.new(port: 9737) # a scanner-incompatible redis process (< 2.7.105)
103
+ key_emitter = RedisCopy::KeyEmitter.implementation.new(source_redis)
104
+ # => <RedisCopy::KeyEmitter::Default: ... >
105
+ key_emitter.keys('schedule:*').to_enum
106
+ # => <Enumerator ...>
107
+ ```
108
+
109
+ The consumer can choose to favor a particular implementation by name:
110
+
111
+ ``` ruby
112
+ key_emitter = RedisCopy::KeyEmitter.implementation(:scanner).new(source_redis)
113
+ # => <RedisCopy::KeyEmitter::Scanner: ... >
114
+ ```
115
+
116
+ And if a compatible implementation cannot be found, an appropriate exception
117
+ is raised:
118
+
119
+ ``` ruby
120
+ key_emitter = RedisCopy::KeyEmitter.implementation(:scanner).new(source_redis)
121
+ # Implements::implementation::NotFound: no compatible implementation for RedisCopy::KeyEmitter>
122
+ ```
123
+
124
+ The implementation finder assumes that implementations loaded later are
125
+ somehow better than those loaded before them, but a consumer can specify
126
+ first preference and fallback groups:
127
+
128
+ ``` ruby
129
+ key_emitter = RedisCopy::KeyEmitter.implementation(:scanner, :auto).new(source_redis)
130
+ # => <RedisCopy::KeyEmitter::Default: ... >
131
+ ```
132
+
133
+ And implementations can be added which are not in the auto load-order and have
134
+ to be explicitly asked for:
135
+
136
+ ``` ruby
137
+ # Like this insane whack-a-mole implementation,
138
+ # Which we wouldn't want anyone to accidentally use:
139
+ class RedisCopy::KeyEmitter::WhackAMole
140
+ Implements RedisCopy::KeyEmitter, auto: false
141
+
142
+ def keys(pattern = '*')
143
+ return enum_for(__method__, pattern) unless block_given?
144
+ while(key = redis.randomkey)
145
+ yield key if glob_match?(pattern, key)
146
+ end
147
+ end
148
+
149
+ # ...
150
+ end
151
+ ```
152
+
153
+ ``` ruby
154
+ key_emitter = RedisCopy::KeyEmitter.implementation(:whack_a_mole).new(source_redis)
155
+ # => <RedisCopy::KeyEmitter::WhackAMole: ... >
156
+ # But it doesn't come back unless you ask for it.
157
+ key_emitter = RedisCopy::KeyEmitter.implementation.new(source_redis)
158
+ # => <RedisCopy::KeyEmitter::Scanner: ... >
159
+ ```
160
+
161
+ # TODO:
162
+
163
+ - Provide tools for testing *all* implementations of an interface.
164
+ - Finalize syntax for the check. A block alone is convenient, but not clear.
165
+ - Finalize scope of check. Allocate and instance_exec? Run all as hooks before `#initialize`?
166
+
167
+ ## Contributing
168
+
169
+ 1. Fork it
170
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
171
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
172
+ 4. Push to the branch (`git push origin my-new-feature`)
173
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ spec.verbose = true
8
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'implements/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'implements'
9
+ spec.version = Implements::VERSION
10
+ spec.authors = ['Ryan Biesemeyer']
11
+ spec.email = ['ryan@simplymeasured.com']
12
+ spec.summary = 'A tool for building and implementing interfaces.'
13
+ spec.description = <<-EODESC.gsub(/^[\w]+/, ' ').squeeze
14
+ Implements is a tool for building modular libraries and tools as
15
+ interfaces, for implementing those interfaces, and ensuring that
16
+ consumers are able to load the best available implementation at
17
+ runtime.
18
+ EODESC
19
+ spec.homepage = 'https://github.com/yaauie/implements'
20
+ spec.license = 'MIT'
21
+
22
+ spec.files = `git ls-files`.split($/)
23
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
24
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_runtime_dependency 'activesupport'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.3', '>= 1.3.5'
30
+ spec.add_development_dependency 'rake'
31
+ spec.add_development_dependency 'rspec', '~> 2.14'
32
+
33
+
34
+ # code quality
35
+ spec.add_development_dependency 'ruby-appraiser-reek'
36
+ spec.add_development_dependency 'ruby-appraiser-rubocop'
37
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ require 'implements/version'
3
+ require 'implements/interface'
4
+ require 'implements/implementation'
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ require 'implements'
3
+
4
+ # Add functionality to Class, which enables us to use
5
+ # `Implements::Implementation`'s ::implements method
6
+ # without having to pre-extend the class.
7
+ class Class
8
+ def implements(*args, &block)
9
+ return super if defined?(super)
10
+
11
+ extend(Implements::Implementation)
12
+ send(__method__, *args, &block)
13
+ end
14
+ private :implements
15
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_support/inflector'
4
+
5
+ require_relative 'implementation/registry'
6
+
7
+ module Implements
8
+ # Implementation: mix into your implementations
9
+ module Implementation
10
+ # An exception raised when an implementation cannot be found.
11
+ NotFound = Class.new(NotImplementedError)
12
+
13
+ private
14
+
15
+ # @api public
16
+ # @param iface [Module(Implements::Interface)]
17
+ # @param options [Hash{Symbol => Object}] - ({}) optional options hash
18
+ # @option options [Boolean] :auto - (true) whether to include this
19
+ # implementation in the interface's default search
20
+ # @option options [String] :as - The canonical name for this
21
+ # implementation, must be unique across all implementations.
22
+ # @option options [#to_s, Array<#to_s>] :groups - one or more named tags
23
+ # for this implementation, used for matching in Interface#implementation.
24
+ #
25
+ # If given, the block will be usde to determine the compatibility of this
26
+ # interface with the arguments that would be passed to the implementation's
27
+ # #initialize method.
28
+ # @yieldparam (@see self#initialize)
29
+ # @yieldreturn [Boolean]
30
+ #
31
+ # @return [void]
32
+ # @raises [TypeError] unless iface is a Implements::Interface Module
33
+ def implements(iface, options = {}, &block)
34
+ unless iface.instance_of?(Module) && iface.kind_of?(Interface)
35
+ fail(TypeError, 'Argument must be a Implements::Interface Module')
36
+ end
37
+
38
+ params = {}
39
+ params[:name] = options.fetch(:as) if options.key?(:as)
40
+ groups = []
41
+ groups << :default unless block_given?
42
+ groups << :auto if options.fetch(:auto, true)
43
+ params[:groups] = groups
44
+
45
+ iface.register_implementation(self, params, &block)
46
+
47
+ include iface
48
+ end
49
+
50
+ # @api private
51
+ def self.extended(klass)
52
+ unless klass.instance_of?(Class)
53
+ fail(TypeError, "expected Class, got #{klass.class}")
54
+ end
55
+ end
56
+
57
+ # @api private
58
+ def self.included(base)
59
+ base && fail(ScriptError, "#{self} supports only extend, not include.")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'set'
4
+
5
+ module Implements
6
+ module Implementation
7
+ # A registry of implementations, held by an interface.
8
+ # @api private
9
+ class Registry
10
+ # @api private
11
+ # @param interface [Interface]
12
+ def initialize(interface)
13
+ @interface = interface
14
+ @elements = []
15
+ end
16
+ attr_reader :interface
17
+
18
+ # Returns an enumerator of elements matching the given selectors,
19
+ # in the order specified; this is used by {Finder#find}.
20
+ # @param selectors [#to_s, Array<#to_s>] - one or more selectors
21
+ # @return [Enumerator<Element>]
22
+ def elements(selectors)
23
+ Enumerator.new do |yielder|
24
+ yielded = Set.new
25
+ Array(selectors).product(@elements) do |selector, element|
26
+ next unless element.match?(selector)
27
+ yielder << element if yielded.add?(element)
28
+ end
29
+ end
30
+ end
31
+
32
+ # @api private
33
+ # @return [Array<String>]
34
+ def list_names
35
+ @elements.map(&:name).compact
36
+ end
37
+
38
+ # @api private
39
+ # @param implementation [Implementation]
40
+ # @param options [Hash{Symbol=>Object}] (see: Element#initialize)
41
+ # @param check [#call, nil]
42
+ def register(implementation, options, check)
43
+ @elements.unshift Element.new(self, implementation, options, check)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ require_relative 'registry/element'
50
+ require_relative 'registry/finder'
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ module Implements
4
+ # An Element in a Registry
5
+ # @api private
6
+ class Implementation::Registry::Element
7
+ # @api private
8
+ # @param registry [Implementation::Registry]
9
+ # @param implementation [Implementation]
10
+ # @param options [Hash{Symbol=>Object}]
11
+ # @param check [#call, nil]
12
+ def initialize(registry, implementation, options, check)
13
+ @registry = registry
14
+ @implementation = implementation
15
+ @options = options
16
+ @check = check
17
+ end
18
+ attr_reader :implementation
19
+
20
+ # @api private
21
+ # @param selector [#===]
22
+ # @return [Boolean]
23
+ def match?(selector)
24
+ selector = selector.to_s if selector.kind_of?(Symbol)
25
+ selector = selector.dasherize if selector.kind_of?(String)
26
+ groups.map(&:to_s).any? { |group| selector === group }
27
+ end
28
+
29
+ # Check the implementation agains the args that would be used
30
+ # to instantiate it.
31
+ # @api private
32
+ # @params *args [Array<Object>]
33
+ # @return [Boolean]
34
+ def check?(*args)
35
+ return true unless @check
36
+ @check.call(*args)
37
+ end
38
+
39
+ # @api private
40
+ # @return [String]
41
+ def name
42
+ groups.first
43
+ end
44
+
45
+ private
46
+
47
+ # @api private
48
+ # @return [Array<String>]
49
+ def groups
50
+ @groups ||= [@options[:name],
51
+ implementation_descriptors,
52
+ @options[:groups]].flatten.compact.map(&:to_s)
53
+ end
54
+
55
+ # @api private
56
+ # @return [Array<String>]
57
+ def implementation_descriptors
58
+ desc = []
59
+ desc << (name = @implementation.name)
60
+ desc << (name && name.sub(/^(::)?#{@registry.interface}::/, ''))
61
+ desc.compact.map(&:underscore).map(&:dasherize).reverse
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module Implements
4
+ # A Finder, plumbed to a Registry.
5
+ # @api private
6
+ class Implementation::Registry::Finder
7
+ # @api private
8
+ # @param registry [Implementation::Registry]
9
+ # @param selectors [Array<#===>] Typically an array of strings
10
+ def initialize(registry, selectors)
11
+ @registry = registry
12
+ @selectors = selectors
13
+ end
14
+
15
+ # Returns an instance of the @registry.interface that supports the given
16
+ # arguments.
17
+ # @api private
18
+ def new(*args, &block)
19
+ implementation = find(*args)
20
+ implementation.new(*args, &block)
21
+ end
22
+
23
+ # Find a suitable implementation of the given interface,
24
+ # given the args that would be passed to its #initialize
25
+ # and our selectors
26
+ # @api private
27
+ def find(*args)
28
+ @registry.elements(@selectors).each do |config|
29
+ next unless config.check?(*args)
30
+ return config.implementation
31
+ end
32
+
33
+ fail(Implementation::NotFound,
34
+ "no compatible implementation for #{inspect}")
35
+ end
36
+
37
+ # @api private
38
+ def inspect
39
+ "<#{@registry.interface}::implementation(#{@selectors.join(', ')})>"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ module Implements
4
+ # Interface: mix into your interfaces.
5
+ module Interface
6
+ # Used to find a suitable implementation
7
+ # @api public
8
+ # @param [*selectors] zero or more selectors to use for finding an
9
+ # implementation of this interface. If none is given, :auto is assumed.
10
+ # @return [Implementation::Registry::Finder]
11
+ def implementation(*selectors)
12
+ selectors << :auto if selectors.empty?
13
+ Implementation::Registry::Finder.new(@implementations, selectors)
14
+ end
15
+
16
+ # Returns a list of implementations by resolvable name.
17
+ # @api public
18
+ # @return [Array<String>]
19
+ def list_implementation_names
20
+ @implementations.list_names.map(&:to_s).uniq
21
+ end
22
+
23
+ # Find an instantiate a suitable implementation on auto mode
24
+ # @see Implementation::Registry::Find#new
25
+ # @api public
26
+ def new(*args, &block)
27
+ implementation(:auto).new(*args, &block)
28
+ end
29
+
30
+ # @api private
31
+ # Used by Implementation#implements
32
+ # @param implements (see Registry#register)
33
+ # @param options (see Registry#register)
34
+ # @param &block (see Registry#register)
35
+ # @return (see Registry#register)
36
+ def register_implementation(implementation, options, &block)
37
+ @implementations.register(implementation, options, block)
38
+ end
39
+
40
+ # Bad things happen when used improperly. Make it harder to get it wrong.
41
+ # @api private
42
+ def self.included(base)
43
+ base && fail(ScriptError, "#{self} supports only extend, not include.")
44
+ end
45
+
46
+ # Set up the interface.
47
+ # @param base [Module]
48
+ # @api private
49
+ def self.extended(base)
50
+ unless base.instance_of?(Module)
51
+ fail(TypeError, "expected Module, got #{base.class}")
52
+ end
53
+
54
+ base.instance_variable_set(:@implementations,
55
+ Implementation::Registry.new(base))
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ # @see ../implements.rb
4
+ module Implements
5
+ # Implements is Semantically Versioned.
6
+ # @see http://semver.org
7
+ VERSION = '0.0.2'
8
+ end
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+ require_relative 'spec_helper'
3
+
4
+ describe Implements do
5
+ context 'On a Widget interface' do
6
+ let!(:interface) do
7
+ interface = Module.new do
8
+ extend Implements::Interface
9
+
10
+ def initialize(number)
11
+ @number = number
12
+ end
13
+
14
+ def wobble() :interface end
15
+ end
16
+
17
+ stub_const('::Widget', interface)
18
+ def Widget.inspect() 'Widget' end
19
+
20
+ interface
21
+ end
22
+
23
+ context 'with two conditional & one default implementations' do
24
+ # order and registry matters, so define the implementations
25
+ # with let! to ensure they are run and memoized before the
26
+ # example is run.
27
+ let!(:default_implementation) do
28
+ Class.new do
29
+ extend(Implements::Implementation)
30
+ implements ::Widget
31
+ end
32
+ end
33
+ let!(:small_implementation) do
34
+ Class.new do
35
+ extend(Implements::Implementation)
36
+ implements ::Widget, as: :small do |number|
37
+ number < 10
38
+ end
39
+ def wobble() :small end
40
+ end
41
+ end
42
+ let!(:large_implementation) do
43
+ Class.new do
44
+ extend(Implements::Implementation)
45
+ implements ::Widget, as: :large do |number|
46
+ number >= 1_000_000
47
+ end
48
+ def wobble() :large end
49
+ end
50
+ end
51
+ let!(:odd_implementation) do
52
+ Class.new do
53
+ extend(Implements::Implementation)
54
+ implements ::Widget, as: :odd do |number|
55
+ number.odd?
56
+ end
57
+ def wobble() :odd end
58
+ end
59
+ end
60
+
61
+ context 'Interface#new' do
62
+ let(:result) { interface.new(input) }
63
+ subject { result }
64
+
65
+ context 'matching the large implementation' do
66
+ let(:input) { 10_000_000 }
67
+ it 'should return the large implementation' do
68
+ expect(result).to be_an_instance_of large_implementation
69
+ end
70
+ it { should be_a Widget }
71
+ its(:wobble) { should be :large } # ensure proper inheritance
72
+ end
73
+
74
+ context 'matching the small implementation' do
75
+ let(:input) { 2 }
76
+ it 'should return the small implementation' do
77
+ expect(result).to be_an_instance_of small_implementation
78
+ end
79
+ it { should be_a Widget }
80
+ its(:wobble) { should be :small } # ensure proper inheritance
81
+ end
82
+
83
+ context 'matching neither small nor large' do
84
+ let(:input) { 1_000 }
85
+ it 'should return the default implementation' do
86
+ expect(result).to be_an_instance_of default_implementation
87
+ end
88
+ it { should be_a Widget }
89
+ its(:wobble) { should be :interface } # ensure proper inheritance
90
+ end
91
+
92
+ context '#implementation.new' do
93
+ let(:result) { interface.implementation(*selectors).new(input) }
94
+ subject { result }
95
+ context 'specifying the large implementation' do
96
+ let(:selectors) { [:large] }
97
+ context 'not matching the large implementation' do
98
+ let(:input) { 2 }
99
+ it 'should raise an appropriate exception' do
100
+ expect do
101
+ result
102
+ end.to raise_error Implements::Implementation::NotFound
103
+ end
104
+ end
105
+ context 'matching the large implementation' do
106
+ let(:input) { 10_000_000 }
107
+ it 'should return the large implementation' do
108
+ expect(result).to be_an_instance_of large_implementation
109
+ end
110
+ end
111
+ end
112
+ context 'specifying the small implementation' do
113
+ let(:selectors) { [:small] }
114
+ context 'not matching the small implementation' do
115
+ let(:input) { 1_000_000 }
116
+ it 'should raise an appropriate exception' do
117
+ expect do
118
+ result
119
+ end.to raise_error Implements::Implementation::NotFound
120
+ end
121
+ end
122
+ context 'matching the small implementation' do
123
+ let(:input) { 2 }
124
+ it 'should return the small implementation' do
125
+ expect(result).to be_an_instance_of small_implementation
126
+ end
127
+ end
128
+ end
129
+ context 'when multiple implementations match' do
130
+ let(:input) { 7 }
131
+ context 'specifying the small or odd implementation' do
132
+ let(:selectors) { [:small, :odd] }
133
+ it 'should favor small' do
134
+ expect(result).to be_an_instance_of small_implementation
135
+ end
136
+ end
137
+ context 'specifying the odd or small implementation' do
138
+ let(:selectors) { [:odd, :small] }
139
+ it 'should favor odd' do
140
+ expect(result).to be_an_instance_of odd_implementation
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'with no implementations' do
149
+ context 'Interface#new' do
150
+ let(:result) { interface.new(input) }
151
+ subject { result }
152
+
153
+ let(:input) { 1_000 }
154
+ it 'should raise an appropriate exception' do
155
+ expect { result }.to raise_error Implements::Implementation::NotFound
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require 'implements'
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: implements
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Biesemeyer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
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: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.3'
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.5
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '1.3'
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: 1.3.5
52
+ - !ruby/object:Gem::Dependency
53
+ name: rake
54
+ requirement: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: '2.14'
84
+ - !ruby/object:Gem::Dependency
85
+ name: ruby-appraiser-reek
86
+ requirement: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: ruby-appraiser-rubocop
102
+ requirement: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ description: ! " Implements is a tol for building modular libraries and tols as\n
117
+ interfaces, for implementing those interfaces, and ensuring that\n consumers are
118
+ able to load the best available implementation at\n runtime.\n"
119
+ email:
120
+ - ryan@simplymeasured.com
121
+ executables: []
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - .githooks/pre-commit/run-ruby-appraiser
126
+ - .gitignore
127
+ - .travis.yml
128
+ - Gemfile
129
+ - LICENSE.txt
130
+ - README.md
131
+ - Rakefile
132
+ - implements.gemspec
133
+ - lib/implements.rb
134
+ - lib/implements/global.rb
135
+ - lib/implements/implementation.rb
136
+ - lib/implements/implementation/registry.rb
137
+ - lib/implements/implementation/registry/element.rb
138
+ - lib/implements/implementation/registry/finder.rb
139
+ - lib/implements/interface.rb
140
+ - lib/implements/version.rb
141
+ - spec/implements_spec.rb
142
+ - spec/spec_helper.rb
143
+ homepage: https://github.com/yaauie/implements
144
+ licenses:
145
+ - MIT
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 1.8.24
165
+ signing_key:
166
+ specification_version: 3
167
+ summary: A tool for building and implementing interfaces.
168
+ test_files:
169
+ - spec/implements_spec.rb
170
+ - spec/spec_helper.rb
171
+ has_rdoc: