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 +7 -0
- data/.gitignore +18 -0
- data/.reek +16 -0
- data/.rspec +1 -0
- data/.rubocop.yml +39 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +166 -0
- data/Guardfile +111 -0
- data/README.md +65 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/duckface-interfaces.gemspec +26 -0
- data/lib/duckface/acts_as_interface.rb +21 -0
- data/lib/duckface/constants.rb +8 -0
- data/lib/duckface/errors.rb +11 -0
- data/lib/duckface/example_class.rb +0 -0
- data/lib/duckface/implementation_methods.rb +9 -0
- data/lib/duckface/method_implementation.rb +31 -0
- data/lib/duckface/object_sugar.rb +16 -0
- data/lib/duckface/parameter_pair.rb +35 -0
- data/lib/duckface/parameter_pairs.rb +23 -0
- data/lib/duckface/rspec.rb +9 -0
- data/lib/duckface/services/check_class_implements_interface.rb +57 -0
- data/lib/duckface/services.rb +6 -0
- data/lib/duckface/version.rb +5 -0
- data/lib/duckface.rb +5 -0
- metadata +113 -0
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
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
|
+

|
|
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
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,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,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,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
|
data/lib/duckface.rb
ADDED
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: []
|