implements 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: