duckface-interfaces 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 91741dbb1648b476dafbc5faafe831caef36726f
4
+ data.tar.gz: 333f3d74a4d3009b19b78cc5375f8885dc421dc9
5
+ SHA512:
6
+ metadata.gz: f1a3bf5007f37d2110189266e20706f855802cbfa3612f31a328451f054308d3aa6078d4d5b5f81a86f2bea5ddea528b20c2a7a85dc3ed3757bbfc5e0bb03338
7
+ data.tar.gz: 4bd4224adc8af55a7f5b95258d12ae9e9c60c91060992d18d7abb9f0f78b7669382f96b6020ab0c178a690df15c09e0092aedf511b9b9e5b6617e7dac1ea62da
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ spec/dummy/db/*.sqlite3
11
+ spec/dummy/db/*.sqlite3-journal
12
+ spec/dummy/db/migrate
13
+ spec/dummy/db/schema.rb
14
+ spec/dummy/log/*.log
15
+ spec/dummy/tmp/
16
+ spec/dummy/public/system/
17
+
18
+ tags*
data/.reek ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ IrresponsibleModule:
3
+ enabled: false
4
+ NilCheck:
5
+ enabled: false
6
+ TooManyStatements:
7
+ enabled: true
8
+ max_statements: 10
9
+ app/controllers:
10
+ NestedIterators:
11
+ max_allowed_nesting: 2
12
+ UnusedPrivateMethod:
13
+ enabled: false
14
+ app/helpers:
15
+ UtilityFunction:
16
+ enabled: false
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,39 @@
1
+ ---
2
+ AllCops:
3
+ TargetRubyVersion: 2.4
4
+ Include:
5
+ - app/**/*.rb
6
+ - lib/**/*.rb
7
+ - spec/**/*.rb
8
+ Exclude:
9
+ - app/assets/**/*
10
+ - bin/**/*
11
+ - client/node_modules/**/*
12
+ - config/**/*
13
+ - coverage/**/*
14
+ - data/**/*
15
+ - db/**/*
16
+ - log/**/*
17
+ - phrase/**/*
18
+ - public/**/*
19
+ - tmp/**/*
20
+ - vendor/**/*
21
+ Documentation:
22
+ Enabled: false
23
+ Metrics/LineLength:
24
+ Max: 100
25
+ Style/MultilineMethodCallIndentation:
26
+ EnforcedStyle: indented
27
+ Style/PercentLiteralDelimiters:
28
+ PreferredDelimiters:
29
+ "%w": "[]"
30
+ RSpec/ExampleLength:
31
+ Max: 10
32
+ require:
33
+ - rubocop-rspec
34
+ RSpec/MultipleExpectations:
35
+ Max: 10
36
+ RSpec/NestedGroups:
37
+ Max: 10
38
+ RSpec/MessageExpectation:
39
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ 0.0.1
2
+
3
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in duckface.gemspec
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem 'guard-livereload', require: false
10
+ gem 'guard-rspec'
11
+ gem 'pry-byebug'
12
+ gem 'rb-fsevent', require: false
13
+ gem 'rb-readline'
14
+ gem 'reek'
15
+ gem 'rspec'
16
+ gem 'rubocop'
17
+ gem 'rubocop-rspec'
18
+ end
19
+
20
+ group :development do
21
+ gem 'spring'
22
+ gem 'spring-watcher-listen'
23
+ end
24
+
25
+ group :test do
26
+ gem 'simplecov'
27
+ gem 'spring-commands-rspec'
28
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,166 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ duckface (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (5.0.4)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (~> 0.7)
12
+ minitest (~> 5.1)
13
+ tzinfo (~> 1.1)
14
+ ast (2.3.0)
15
+ axiom-types (0.1.1)
16
+ descendants_tracker (~> 0.0.4)
17
+ ice_nine (~> 0.11.0)
18
+ thread_safe (~> 0.3, >= 0.3.1)
19
+ byebug (9.0.6)
20
+ codeclimate-engine-rb (0.4.0)
21
+ virtus (~> 1.0)
22
+ coderay (1.1.1)
23
+ coercible (1.0.0)
24
+ descendants_tracker (~> 0.0.1)
25
+ concurrent-ruby (1.0.5)
26
+ descendants_tracker (0.0.4)
27
+ thread_safe (~> 0.3, >= 0.3.1)
28
+ diff-lcs (1.3)
29
+ docile (1.1.5)
30
+ em-websocket (0.5.1)
31
+ eventmachine (>= 0.12.9)
32
+ http_parser.rb (~> 0.6.0)
33
+ equalizer (0.0.11)
34
+ eventmachine (1.2.3)
35
+ ffi (1.9.18)
36
+ formatador (0.2.5)
37
+ guard (2.14.1)
38
+ formatador (>= 0.2.4)
39
+ listen (>= 2.7, < 4.0)
40
+ lumberjack (~> 1.0)
41
+ nenv (~> 0.1)
42
+ notiffany (~> 0.0)
43
+ pry (>= 0.9.12)
44
+ shellany (~> 0.0)
45
+ thor (>= 0.18.1)
46
+ guard-compat (1.2.1)
47
+ guard-livereload (2.5.2)
48
+ em-websocket (~> 0.5)
49
+ guard (~> 2.8)
50
+ guard-compat (~> 1.0)
51
+ multi_json (~> 1.8)
52
+ guard-rspec (4.7.3)
53
+ guard (~> 2.1)
54
+ guard-compat (~> 1.1)
55
+ rspec (>= 2.99.0, < 4.0)
56
+ http_parser.rb (0.6.0)
57
+ i18n (0.9.1)
58
+ concurrent-ruby (~> 1.0)
59
+ ice_nine (0.11.2)
60
+ json (2.1.0)
61
+ listen (3.1.5)
62
+ rb-fsevent (~> 0.9, >= 0.9.4)
63
+ rb-inotify (~> 0.9, >= 0.9.7)
64
+ ruby_dep (~> 1.2)
65
+ lumberjack (1.0.12)
66
+ method_source (0.8.2)
67
+ minitest (5.10.2)
68
+ multi_json (1.12.1)
69
+ nenv (0.3.0)
70
+ notiffany (0.1.1)
71
+ nenv (~> 0.1)
72
+ shellany (~> 0.0)
73
+ parallel (1.11.2)
74
+ parser (2.4.0.0)
75
+ ast (~> 2.2)
76
+ powerpack (0.1.1)
77
+ pry (0.10.4)
78
+ coderay (~> 1.1.0)
79
+ method_source (~> 0.8.1)
80
+ slop (~> 3.4)
81
+ pry-byebug (3.4.2)
82
+ byebug (~> 9.0)
83
+ pry (~> 0.10)
84
+ rainbow (2.2.2)
85
+ rake
86
+ rake (10.5.0)
87
+ rb-fsevent (0.9.8)
88
+ rb-inotify (0.9.10)
89
+ ffi (>= 0.5.0, < 2)
90
+ rb-readline (0.5.4)
91
+ reek (4.7.1)
92
+ codeclimate-engine-rb (~> 0.4.0)
93
+ parser (>= 2.4.0.0, < 2.5)
94
+ rainbow (~> 2.0)
95
+ rspec (3.6.0)
96
+ rspec-core (~> 3.6.0)
97
+ rspec-expectations (~> 3.6.0)
98
+ rspec-mocks (~> 3.6.0)
99
+ rspec-core (3.6.0)
100
+ rspec-support (~> 3.6.0)
101
+ rspec-expectations (3.6.0)
102
+ diff-lcs (>= 1.2.0, < 2.0)
103
+ rspec-support (~> 3.6.0)
104
+ rspec-mocks (3.6.0)
105
+ diff-lcs (>= 1.2.0, < 2.0)
106
+ rspec-support (~> 3.6.0)
107
+ rspec-support (3.6.0)
108
+ rubocop (0.49.1)
109
+ parallel (~> 1.10)
110
+ parser (>= 2.3.3.1, < 3.0)
111
+ powerpack (~> 0.1)
112
+ rainbow (>= 1.99.1, < 3.0)
113
+ ruby-progressbar (~> 1.7)
114
+ unicode-display_width (~> 1.0, >= 1.0.1)
115
+ rubocop-rspec (1.15.1)
116
+ rubocop (>= 0.42.0)
117
+ ruby-progressbar (1.8.1)
118
+ ruby_dep (1.5.0)
119
+ shellany (0.0.1)
120
+ simplecov (0.14.1)
121
+ docile (~> 1.1.0)
122
+ json (>= 1.8, < 3)
123
+ simplecov-html (~> 0.10.0)
124
+ simplecov-html (0.10.1)
125
+ slop (3.6.0)
126
+ spring (2.0.2)
127
+ activesupport (>= 4.2)
128
+ spring-commands-rspec (1.0.4)
129
+ spring (>= 0.9.1)
130
+ spring-watcher-listen (2.0.1)
131
+ listen (>= 2.7, < 4.0)
132
+ spring (>= 1.2, < 3.0)
133
+ thor (0.19.4)
134
+ thread_safe (0.3.6)
135
+ tzinfo (1.2.3)
136
+ thread_safe (~> 0.1)
137
+ unicode-display_width (1.2.1)
138
+ virtus (1.0.5)
139
+ axiom-types (~> 0.1)
140
+ coercible (~> 1.0)
141
+ descendants_tracker (~> 0.0, >= 0.0.3)
142
+ equalizer (~> 0.0, >= 0.0.9)
143
+
144
+ PLATFORMS
145
+ ruby
146
+
147
+ DEPENDENCIES
148
+ bundler (>= 1.13)
149
+ duckface!
150
+ guard-livereload
151
+ guard-rspec
152
+ pry-byebug
153
+ rake (>= 10.0)
154
+ rb-fsevent
155
+ rb-readline
156
+ reek
157
+ rspec
158
+ rubocop
159
+ rubocop-rspec
160
+ simplecov
161
+ spring
162
+ spring-commands-rspec
163
+ spring-watcher-listen
164
+
165
+ BUNDLED WITH
166
+ 1.16.1
data/Guardfile ADDED
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ # directories %w(app lib config test spec features) \
8
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
+
10
+ ## Note: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ guard 'livereload' do
21
+ extensions = {
22
+ css: :css,
23
+ scss: :css,
24
+ sass: :css,
25
+ js: :js,
26
+ coffee: :js,
27
+ html: :html,
28
+ png: :png,
29
+ gif: :gif,
30
+ jpg: :jpg,
31
+ jpeg: :jpeg,
32
+ # less: :less, # uncomment if you want LESS stylesheets done in browser
33
+ }
34
+
35
+ rails_view_exts = %w[erb haml slim]
36
+
37
+ # file types LiveReload may optimize refresh for
38
+ compiled_exts = extensions.values.uniq
39
+ watch(%r{public/.+\.(#{compiled_exts * '|'})})
40
+
41
+ extensions.each do |ext, type|
42
+ watch(%r{
43
+ (?:app|vendor)
44
+ (?:/assets/\w+/(?<path>[^.]+) # path+base without extension
45
+ (?<ext>\.#{ext})) # matching extension (must be first encountered)
46
+ (?:\.\w+|$) # other extensions
47
+ }x) do |m|
48
+ path = m[1]
49
+ "/assets/#{path}.#{type}"
50
+ end
51
+ end
52
+
53
+ # file needing a full reload of the page anyway
54
+ watch(%r{app/views/.+\.(#{rails_view_exts * '|'})$})
55
+ watch(%r{app/helpers/.+\.rb})
56
+ watch(%r{config/locales/.+\.yml})
57
+ end
58
+
59
+ # Note: The cmd option is now required due to the increasing number of ways
60
+ # rspec may be run, below are examples of the most common uses.
61
+ # * bundler: 'bundle exec rspec'
62
+ # * bundler binstubs: 'bin/rspec'
63
+ # * spring: 'bin/rspec' (This will use spring if running and you have
64
+ # installed the spring binstubs per the docs)
65
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
66
+ # * 'just' rspec: 'rspec'
67
+
68
+ guard :rspec, cmd: 'bundle exec rspec' do
69
+ require 'guard/rspec/dsl'
70
+ dsl = Guard::RSpec::Dsl.new(self)
71
+
72
+ # Feel free to open issues for suggestions and improvements
73
+
74
+ # RSpec files
75
+ rspec = dsl.rspec
76
+ watch(rspec.spec_helper) { rspec.spec_dir }
77
+ watch(rspec.spec_support) { rspec.spec_dir }
78
+ watch(rspec.spec_files)
79
+
80
+ # Ruby files
81
+ ruby = dsl.ruby
82
+ dsl.watch_spec_files_for(ruby.lib_files)
83
+
84
+ # Rails files
85
+ rails = dsl.rails(view_extensions: %w[erb haml slim])
86
+ dsl.watch_spec_files_for(rails.app_files)
87
+ dsl.watch_spec_files_for(rails.views)
88
+
89
+ watch(rails.controllers) do |m|
90
+ [
91
+ rspec.spec.call("routing/#{m[1]}_routing"),
92
+ rspec.spec.call("controllers/#{m[1]}_controller"),
93
+ rspec.spec.call("acceptance/#{m[1]}")
94
+ ]
95
+ end
96
+
97
+ # Rails config changes
98
+ watch(rails.spec_helper) { rspec.spec_dir }
99
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
100
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
101
+
102
+ # Capybara features specs
103
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
104
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
105
+
106
+ # Turnip features and steps
107
+ watch(%r{^spec/acceptance/(.+)\.feature$})
108
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
109
+ Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
110
+ end
111
+ end
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ ![duckface](https://user-images.githubusercontent.com/2643026/40590182-811ac3bc-61f2-11e8-814a-b235c51fd52c.jpg)
2
+
3
+ # Duckface
4
+
5
+ A collection of tools to enforce duck typing based interfaces in Ruby.
6
+
7
+ ## Configure
8
+
9
+ ### RSpec
10
+
11
+ `spec/spec_helper.rb`
12
+
13
+ ```ruby
14
+ require 'duckface/rspec'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Define an interface
20
+
21
+ ```ruby
22
+ require 'duckface'
23
+
24
+ module MyInterface
25
+ extend Duckface::ActsAsInterface
26
+
27
+ exclude_methods_from_interface_enforcement :ignoreable_method_a, :ignoreable_method_b
28
+
29
+ def say_my_name(_name)
30
+ raise NotImplementedMethod
31
+ end
32
+
33
+ def ignoreable_method_a
34
+ puts 'I can be ignored'
35
+ end
36
+
37
+ def ignoreable_method_b
38
+ puts 'And so can I'
39
+ end
40
+ end
41
+ ```
42
+
43
+ ### Define an implementation
44
+
45
+ ```ruby
46
+ require 'duckface'
47
+
48
+ class MyImplementation
49
+ implements_interface MyInterface
50
+
51
+ def say_my_name(name)
52
+ puts name
53
+ end
54
+ end
55
+ ```
56
+
57
+ ### Test that an implementation correctly implements an interface
58
+
59
+ ```ruby
60
+ require 'spec_helper'
61
+
62
+ describe MyImplementation
63
+ it_behaves_like 'it implements', MyInterface
64
+ end
65
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "duckface"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,26 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('../lib', __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'duckface/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'duckface-interfaces'
10
+ spec.version = Duckface::VERSION
11
+ spec.authors = ['Bellroy Tech Team']
12
+ spec.email = ['tech@bellroy.com']
13
+
14
+ spec.summary = 'Duckface'
15
+ spec.description = 'Duck typing + Interfaces'
16
+ spec.homepage = 'https://github.com/samuelgiles/duckface'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '>= 1.13'
24
+ spec.add_development_dependency 'rake', '>= 10.0'
25
+ spec.add_development_dependency 'rspec', '>= 3.0'
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/constants'
4
+
5
+ module Duckface
6
+ module ActsAsInterface
7
+ def exclude_methods_from_interface_enforcement(*method_names)
8
+ class_variable_set(:@@unenforced_methods, method_names)
9
+ end
10
+
11
+ def methods_that_should_be_implemented
12
+ expected_public_instance_methods = public_instance_methods(false)
13
+ unenforced_methods = begin
14
+ class_variable_get(:@@unenforced_methods)
15
+ rescue NameError
16
+ []
17
+ end
18
+ expected_public_instance_methods - (unenforced_methods || [])
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ module Constants
5
+ UNENFORCED_METHODS_CONSTANT_NAME = 'UNENFORCED_METHODS'
6
+ IGNORABLE_PARAMETERS = [%i[block block], %i[rest args]].freeze
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ module Errors
5
+ # Raised when a class does not implement a method
6
+ class InterfaceMethodNotImplementedError < NotImplementedError; end
7
+ # Raised when an implementation method does not have the same signature
8
+ # as the interface
9
+ class ImplementationSignatureIncorrectError < NotImplementedError; end
10
+ end
11
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ module ImplementationMethods
5
+ def check_it_implements(interface_class)
6
+ Duckface::Services::CheckClassImplementsInterface.new(self, interface_class).perform
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/constants'
4
+ require 'duckface/parameter_pairs'
5
+
6
+ module Duckface
7
+ class MethodImplementation
8
+ def initialize(klass, method_name)
9
+ @klass = klass
10
+ @method_name = method_name
11
+ end
12
+
13
+ def parameters_for_comparison
14
+ @parameters_for_comparison ||= ParameterPairs.new(parameters).for_comparison
15
+ end
16
+
17
+ def owner
18
+ @owner ||= implementation.owner
19
+ end
20
+
21
+ private
22
+
23
+ def implementation
24
+ @implementation ||= @klass.public_instance_method(@method_name)
25
+ end
26
+
27
+ def parameters
28
+ implementation.parameters - Constants::IGNORABLE_PARAMETERS
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/services/check_class_implements_interface'
4
+ require 'duckface/implementation_methods'
5
+
6
+ module Duckface
7
+ # Provides methods on any class for indicate usage of interfaces
8
+ module ObjectSugar
9
+ def implements_interface(interface_class)
10
+ extend Duckface::ImplementationMethods
11
+ include interface_class
12
+ end
13
+ end
14
+ end
15
+
16
+ Object.extend(Duckface::ObjectSugar)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ # Takes a method parameters and prepares them for comparison
5
+ class ParameterPair
6
+ def initialize(parameter_pair)
7
+ @parameter_pair = parameter_pair
8
+ end
9
+
10
+ def for_comparison
11
+ [@parameter_pair.first, argument_name_without_leading_underscore]
12
+ end
13
+
14
+ private
15
+
16
+ UNDERSCORE = '_'
17
+ FIRST_CHARACTER = 0
18
+
19
+ # Leading underscores are used to indicate a parameter isn't used
20
+ def argument_name_without_leading_underscore
21
+ name = if argument_name_string[FIRST_CHARACTER] == UNDERSCORE
22
+ argument_name_string.reverse.chop.reverse
23
+ else
24
+ argument_name_string
25
+ end
26
+ name.to_sym
27
+ end
28
+
29
+ def argument_name_string
30
+ @parameter_pair.last.to_s
31
+ end
32
+
33
+ private_constant :UNDERSCORE
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/parameter_pair'
4
+
5
+ module Duckface
6
+ class ParameterPairs
7
+ def initialize(parameters)
8
+ @parameters = parameters
9
+ end
10
+
11
+ def for_comparison
12
+ parameter_pairs.map(&:for_comparison)
13
+ end
14
+
15
+ private
16
+
17
+ def parameter_pairs
18
+ @parameters.map do |parameter_pair|
19
+ ParameterPair.new(parameter_pair)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples 'it implements' do |interface_class|
4
+ describe "implements interface #{interface_class.name}" do
5
+ subject(:check_it_implements) { described_class.check_it_implements(interface_class) }
6
+
7
+ it { is_expected.to be true }
8
+ end
9
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/errors'
4
+ require 'duckface/method_implementation'
5
+
6
+ module Duckface
7
+ module Services
8
+ class CheckClassImplementsInterface
9
+ def initialize(implementation_class, interface_class)
10
+ @implementation_class = implementation_class
11
+ @interface_class = interface_class
12
+ end
13
+
14
+ def perform
15
+ methods_that_should_be_implemented.each do |method_name|
16
+ check_method_is_implemented(method_name)
17
+ check_method_has_correct_signature(method_name)
18
+ end
19
+ true
20
+ end
21
+
22
+ private
23
+
24
+ def check_method_is_implemented(method_name)
25
+ return if method_implemented?(method_name)
26
+ raise Errors::InterfaceMethodNotImplementedError, "##{method_name} is not implemented"
27
+ end
28
+
29
+ def check_method_has_correct_signature(method_name)
30
+ return if method_has_correct_signature?(method_name)
31
+ raise Errors::ImplementationSignatureIncorrectError,
32
+ "##{method_name} does not have the correct signature"
33
+ end
34
+
35
+ def methods_that_should_be_implemented
36
+ @methods_that_should_be_implemented ||= @interface_class.methods_that_should_be_implemented
37
+ end
38
+
39
+ def method_implemented?(method_name)
40
+ method_implementation(method_name).owner != @interface_class
41
+ end
42
+
43
+ def method_has_correct_signature?(method_name)
44
+ method_implementation(method_name).parameters_for_comparison ==
45
+ interface_implementation(method_name).parameters_for_comparison
46
+ end
47
+
48
+ def method_implementation(method_name)
49
+ MethodImplementation.new(@implementation_class, method_name)
50
+ end
51
+
52
+ def interface_implementation(method_name)
53
+ MethodImplementation.new(@interface_class, method_name)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ module Services
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Duckface
4
+ VERSION = '0.0.1'
5
+ end
data/lib/duckface.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'duckface/version'
4
+ require 'duckface/object_sugar'
5
+ require 'duckface/acts_as_interface'
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: duckface-interfaces
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bellroy Tech Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Duck typing + Interfaces
56
+ email:
57
+ - tech@bellroy.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".reek"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".ruby-version"
67
+ - CHANGELOG.md
68
+ - Gemfile
69
+ - Gemfile.lock
70
+ - Guardfile
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - duckface-interfaces.gemspec
76
+ - lib/duckface.rb
77
+ - lib/duckface/acts_as_interface.rb
78
+ - lib/duckface/constants.rb
79
+ - lib/duckface/errors.rb
80
+ - lib/duckface/example_class.rb
81
+ - lib/duckface/implementation_methods.rb
82
+ - lib/duckface/method_implementation.rb
83
+ - lib/duckface/object_sugar.rb
84
+ - lib/duckface/parameter_pair.rb
85
+ - lib/duckface/parameter_pairs.rb
86
+ - lib/duckface/rspec.rb
87
+ - lib/duckface/services.rb
88
+ - lib/duckface/services/check_class_implements_interface.rb
89
+ - lib/duckface/version.rb
90
+ homepage: https://github.com/samuelgiles/duckface
91
+ licenses: []
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.6.13
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Duckface
113
+ test_files: []