strict 1.0.0 → 1.1.0
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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +65 -0
- data/lib/strict/accessor/attributes.rb +1 -1
- data/lib/strict/accessor/module.rb +1 -1
- data/lib/strict/attributes/{configured.rb → class.rb} +5 -1
- data/lib/strict/attributes/coercer.rb +32 -0
- data/lib/strict/attributes/dsl.rb +1 -0
- data/lib/strict/coercers/array.rb +22 -0
- data/lib/strict/coercers/hash.rb +34 -0
- data/lib/strict/dsl/coercible.rb +14 -0
- data/lib/strict/implementation_does_not_conform_error.rb +88 -0
- data/lib/strict/interface.rb +21 -0
- data/lib/strict/interfaces/instance.rb +54 -0
- data/lib/strict/method.rb +20 -0
- data/lib/strict/method_definition_error.rb +2 -2
- data/lib/strict/methods/dsl.rb +1 -0
- data/lib/strict/reader/attributes.rb +1 -1
- data/lib/strict/reader/module.rb +1 -1
- data/lib/strict/version.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bf58678470f8cc840c19af0f64ce47ea55abe7124779d1b37ebe10707708d41
|
4
|
+
data.tar.gz: 02fb60b1183ab1d5eb90c8438a2271512db588438e75f1f89958e67325788360
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e333d0836e79b87dfe89fbdc88c915506609a815b1bfce94b4432f40cdf87696c94da52294d3410d11faa48de6c2197194f8be1216cc50ccce2fc9871ab543fc
|
7
|
+
data.tar.gz: 14c429b220b1a1c890f164daf50dbda22b86d85659abd1c4a8980abd90785d6c52efc7840af85a75740f2dd928c45861d1244061eb703f5e1461b4cabbe8c2f9
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
strict (1.
|
4
|
+
strict (1.1.0)
|
5
5
|
zeitwerk (~> 2.6)
|
6
6
|
|
7
7
|
GEM
|
@@ -45,7 +45,7 @@ GEM
|
|
45
45
|
rubocop (~> 1.0)
|
46
46
|
ruby-progressbar (1.11.0)
|
47
47
|
unicode-display_width (2.3.0)
|
48
|
-
zeitwerk (2.6.
|
48
|
+
zeitwerk (2.6.1)
|
49
49
|
|
50
50
|
PLATFORMS
|
51
51
|
arm64-darwin-21
|
data/README.md
CHANGED
@@ -108,6 +108,71 @@ UpdateEmail.new.call(user_id: "123", email: "456")
|
|
108
108
|
# => Strict::MethodReturnError
|
109
109
|
```
|
110
110
|
|
111
|
+
### `Strict::Interface`
|
112
|
+
|
113
|
+
```rb
|
114
|
+
class Storage
|
115
|
+
extend Strict::Interface
|
116
|
+
|
117
|
+
expose(:write) do
|
118
|
+
key String
|
119
|
+
contents String
|
120
|
+
returns Boolean()
|
121
|
+
end
|
122
|
+
|
123
|
+
expose(:read) do
|
124
|
+
key String
|
125
|
+
returns AnyOf(String, nil)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module Storages
|
130
|
+
class Memory
|
131
|
+
def initialize
|
132
|
+
@storage = {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def write(key:, contents:)
|
136
|
+
storage[key] = contents
|
137
|
+
true
|
138
|
+
end
|
139
|
+
|
140
|
+
def read(key:)
|
141
|
+
storage[key]
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
attr_reader :storage
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
storage = Storage.new(Storages::Memory.new)
|
151
|
+
# => #<Storage implementation=#<Storages::Memory>>
|
152
|
+
|
153
|
+
storage.write(key: "some/path/to/file.rb", contents: "Hello")
|
154
|
+
# => true
|
155
|
+
|
156
|
+
storage.write(key: "some/path/to/file.rb", contents: {})
|
157
|
+
# => Strict::MethodCallError
|
158
|
+
|
159
|
+
storage.read(key: "some/path/to/file.rb")
|
160
|
+
# => "Hello"
|
161
|
+
|
162
|
+
storage.read(key: "some/path/to/other.rb")
|
163
|
+
# => nil
|
164
|
+
|
165
|
+
module Storages
|
166
|
+
class Wat
|
167
|
+
def write(key:)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
storage = Storage.new(Storages::Wat.new)
|
173
|
+
# => Strict::ImplementationDoesNotConformError
|
174
|
+
```
|
175
|
+
|
111
176
|
## Development
|
112
177
|
|
113
178
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -10,7 +10,7 @@ module Strict
|
|
10
10
|
super()
|
11
11
|
|
12
12
|
@configuration = configuration
|
13
|
-
const_set(Strict::Attributes::
|
13
|
+
const_set(Strict::Attributes::Class::CONSTANT, configuration)
|
14
14
|
configuration.attributes.each do |attribute|
|
15
15
|
module_eval(
|
16
16
|
"def #{attribute.name} = #{attribute.instance_variable}", # def name = @instance_variable
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
module Strict
|
4
4
|
module Attributes
|
5
|
-
module
|
5
|
+
module Class
|
6
6
|
CONSTANT = :STRICT_INTERNAL_ATTRIBUTES_CONFIGURATION__
|
7
7
|
|
8
8
|
def strict_attributes
|
9
9
|
self::STRICT_INTERNAL_ATTRIBUTES_CONFIGURATION__
|
10
10
|
end
|
11
|
+
|
12
|
+
def coercer
|
13
|
+
Coercer.new(self)
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Attributes
|
5
|
+
class Coercer
|
6
|
+
attr_reader :attributes_class
|
7
|
+
|
8
|
+
def initialize(attributes_class)
|
9
|
+
@attributes_class = attributes_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(value)
|
13
|
+
return value if value.nil? || !value.respond_to?(:to_h)
|
14
|
+
|
15
|
+
coerce(value.to_h)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
NOT_PROVIDED = ::Object.new.freeze
|
21
|
+
|
22
|
+
def coerce(hash)
|
23
|
+
attributes_class.new(
|
24
|
+
**attributes_class.strict_attributes.each_with_object({}) do |attribute, attributes|
|
25
|
+
value = hash.fetch(attribute.name) { hash.fetch(attribute.name.to_s, NOT_PROVIDED) }
|
26
|
+
attributes[attribute.name] = value unless value.equal?(NOT_PROVIDED)
|
27
|
+
end
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Coercers
|
5
|
+
class Array
|
6
|
+
attr_reader :element_coercer
|
7
|
+
|
8
|
+
def initialize(element_coercer = nil)
|
9
|
+
@element_coercer = element_coercer
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(value)
|
13
|
+
return value if value.nil? || !value.respond_to?(:to_a)
|
14
|
+
|
15
|
+
array = value.to_a
|
16
|
+
return array unless element_coercer
|
17
|
+
|
18
|
+
array.map { |element| element_coercer.call(element) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Coercers
|
5
|
+
class Hash
|
6
|
+
attr_reader :key_coercer, :value_coercer
|
7
|
+
|
8
|
+
def initialize(key_coercer = nil, value_coercer = nil)
|
9
|
+
@key_coercer = key_coercer
|
10
|
+
@value_coercer = value_coercer
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(value)
|
14
|
+
return value if value.nil? || !value.respond_to?(:to_h)
|
15
|
+
|
16
|
+
if key_coercer && value_coercer
|
17
|
+
coerce_keys_and_values(value.to_h)
|
18
|
+
elsif key_coercer
|
19
|
+
coerce_keys(value.to_h)
|
20
|
+
elsif value_coercer
|
21
|
+
coerce_values(value.to_h)
|
22
|
+
else
|
23
|
+
value.to_h
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def coerce_keys_and_values(hash) = hash.to_h { |key, value| [key_coercer.call(key), value_coercer.call(value)] }
|
30
|
+
def coerce_keys(hash) = hash.transform_keys { |key| key_coercer.call(key) }
|
31
|
+
def coerce_values(hash) = hash.transform_values { |value| value_coercer.call(value) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Dsl
|
5
|
+
module Coercible
|
6
|
+
# rubocop:disable Naming/MethodName
|
7
|
+
|
8
|
+
def ToArray(with: nil) = ::Strict::Coercers::Array.new(with)
|
9
|
+
def ToHash(with_keys: nil, with_values: nil) = ::Strict::Coercers::Hash.new(with_keys, with_values)
|
10
|
+
|
11
|
+
# rubocop:enable Naming/MethodName
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
class ImplementationDoesNotConformError < Error
|
5
|
+
attr_reader :interface, :receiver, :missing_methods, :invalid_method_definitions
|
6
|
+
|
7
|
+
def initialize(interface:, receiver:, missing_methods:, invalid_method_definitions:)
|
8
|
+
super(message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:))
|
9
|
+
|
10
|
+
@interface = interface
|
11
|
+
@receiver = receiver
|
12
|
+
@missing_methods = missing_methods
|
13
|
+
@invalid_method_definitions = invalid_method_definitions
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def message_from(interface:, receiver:, missing_methods:, invalid_method_definitions:)
|
19
|
+
details = [
|
20
|
+
missing_methods_message_from(missing_methods),
|
21
|
+
invalid_method_definitions_message_from(invalid_method_definitions)
|
22
|
+
].compact.join("\n")
|
23
|
+
|
24
|
+
case receiver
|
25
|
+
when ::Class, ::Module
|
26
|
+
"#{receiver}'s implementation of #{interface} does not conform:\n#{details}"
|
27
|
+
else
|
28
|
+
"#{receiver.class}'s implementation of #{interface} does not conform:\n#{details}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def missing_methods_message_from(missing_methods)
|
33
|
+
return nil unless missing_methods
|
34
|
+
|
35
|
+
details = missing_methods.map do |method_name|
|
36
|
+
" - #{method_name}"
|
37
|
+
end.join("\n")
|
38
|
+
|
39
|
+
" Some methods exposed in the interface were not defined in the implementation:\n#{details}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def invalid_method_definitions_message_from(invalid_method_definitions)
|
43
|
+
return nil if invalid_method_definitions.empty?
|
44
|
+
|
45
|
+
methods_details = invalid_method_definitions.map do |name, invalid_method_definition|
|
46
|
+
method_details = [
|
47
|
+
missing_parameters_message_from(invalid_method_definition.fetch(:missing_parameters)),
|
48
|
+
additional_parameters_message_from(invalid_method_definition.fetch(:additional_parameters)),
|
49
|
+
non_keyword_parameters_message_from(invalid_method_definition.fetch(:non_keyword_parameters))
|
50
|
+
].compact.join("\n")
|
51
|
+
|
52
|
+
" #{name}:\n#{method_details}"
|
53
|
+
end.join("\n")
|
54
|
+
|
55
|
+
" Some methods defined in the implementation did not conform to their interface:\n#{methods_details}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def missing_parameters_message_from(missing_parameters)
|
59
|
+
return nil unless missing_parameters.any?
|
60
|
+
|
61
|
+
details = missing_parameters.map do |parameter_name|
|
62
|
+
" - #{parameter_name}"
|
63
|
+
end.join("\n")
|
64
|
+
|
65
|
+
" Some parameters were expected, but were not in the parameter list:\n#{details}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def additional_parameters_message_from(additional_parameters)
|
69
|
+
return nil unless additional_parameters.any?
|
70
|
+
|
71
|
+
details = additional_parameters.map do |parameter_name|
|
72
|
+
" - #{parameter_name}"
|
73
|
+
end.join("\n")
|
74
|
+
|
75
|
+
" Some parameters were not expected, but were in the parameter list:\n#{details}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def non_keyword_parameters_message_from(non_keyword_parameters)
|
79
|
+
return nil unless non_keyword_parameters.any?
|
80
|
+
|
81
|
+
details = non_keyword_parameters.map do |parameter_name|
|
82
|
+
" - #{parameter_name}"
|
83
|
+
end.join("\n")
|
84
|
+
|
85
|
+
" Some parameters were not keywords, but only keywords are supported:\n#{details}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Interface
|
5
|
+
def self.extended(mod)
|
6
|
+
mod.extend(Strict::Method)
|
7
|
+
mod.include(Interfaces::Instance)
|
8
|
+
end
|
9
|
+
|
10
|
+
def expose(method_name, &)
|
11
|
+
sig = sig(&)
|
12
|
+
parameter_list = sig.parameters.map { |parameter| "#{parameter.name}:" }.join(", ")
|
13
|
+
|
14
|
+
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
15
|
+
def #{method_name}(#{parameter_list}, &block) # def method_name(one:, two:, three:, &block)
|
16
|
+
implementation.#{method_name}(#{parameter_list}, &block) # implementation.method_name(one:, two:, three:, &block)
|
17
|
+
end # end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Strict
|
4
|
+
module Interfaces
|
5
|
+
module Instance
|
6
|
+
attr_reader :implementation
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
9
|
+
def initialize(implementation)
|
10
|
+
missing_methods = nil
|
11
|
+
invalid_method_definitions = Hash.new do |h, k|
|
12
|
+
h[k] = { additional_parameters: [], missing_parameters: [], non_keyword_parameters: [] }
|
13
|
+
end
|
14
|
+
|
15
|
+
self.class.strict_instance_methods.each do |method_name, strict_method|
|
16
|
+
unless implementation.respond_to?(method_name)
|
17
|
+
missing_methods ||= []
|
18
|
+
missing_methods << method_name
|
19
|
+
next
|
20
|
+
end
|
21
|
+
|
22
|
+
expected_parameters = Set.new(strict_method.parameters.map(&:name))
|
23
|
+
defined_parameters = Set.new
|
24
|
+
|
25
|
+
implementation.method(method_name).parameters.each do |kind, parameter_name|
|
26
|
+
next if kind == :block
|
27
|
+
|
28
|
+
if expected_parameters.include?(parameter_name)
|
29
|
+
defined_parameters.add(parameter_name)
|
30
|
+
invalid_method_definitions[method_name][:non_keyword_parameters] << parameter_name if kind != :keyreq
|
31
|
+
else
|
32
|
+
invalid_method_definitions[method_name][:additional_parameters] << parameter_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
missing_parameters = expected_parameters - defined_parameters
|
37
|
+
invalid_method_definitions[method_name][:missing_parameters] = missing_parameters if missing_parameters.any?
|
38
|
+
end
|
39
|
+
|
40
|
+
if missing_methods || !invalid_method_definitions.empty?
|
41
|
+
raise Strict::ImplementationDoesNotConformError.new(
|
42
|
+
interface: self.class,
|
43
|
+
receiver: implementation,
|
44
|
+
missing_methods:,
|
45
|
+
invalid_method_definitions:
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
@implementation = implementation
|
50
|
+
end
|
51
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/strict/method.rb
CHANGED
@@ -13,6 +13,24 @@ module Strict
|
|
13
13
|
instance.instance_variable_set(:@__strict_method_internal_last_sig_configuration, Methods::Dsl.run(&))
|
14
14
|
end
|
15
15
|
|
16
|
+
def strict_class_methods
|
17
|
+
instance = singleton_class? ? self : singleton_class
|
18
|
+
if instance.instance_variable_defined?(:@__strict_method_internal_class_methods)
|
19
|
+
instance.instance_variable_get(:@__strict_method_internal_class_methods)
|
20
|
+
else
|
21
|
+
instance.instance_variable_set(:@__strict_method_internal_class_methods, {})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def strict_instance_methods
|
26
|
+
instance = singleton_class? ? self : singleton_class
|
27
|
+
if instance.instance_variable_defined?(:@__strict_method_internal_instance_methods)
|
28
|
+
instance.instance_variable_get(:@__strict_method_internal_instance_methods)
|
29
|
+
else
|
30
|
+
instance.instance_variable_set(:@__strict_method_internal_instance_methods, {})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
16
34
|
# rubocop:disable Metrics/MethodLength
|
17
35
|
def singleton_method_added(method_name)
|
18
36
|
super
|
@@ -28,6 +46,7 @@ module Strict
|
|
28
46
|
instance: false
|
29
47
|
)
|
30
48
|
verifiable_method.verify_definition!
|
49
|
+
strict_class_methods[method_name] = verifiable_method
|
31
50
|
singleton_class.prepend(Methods::Module.new(verifiable_method))
|
32
51
|
end
|
33
52
|
|
@@ -45,6 +64,7 @@ module Strict
|
|
45
64
|
instance: true
|
46
65
|
)
|
47
66
|
verifiable_method.verify_definition!
|
67
|
+
strict_instance_methods[method_name] = verifiable_method
|
48
68
|
prepend(Methods::Module.new(verifiable_method))
|
49
69
|
end
|
50
70
|
# rubocop:enable Metrics/MethodLength
|
@@ -30,7 +30,7 @@ module Strict
|
|
30
30
|
" - #{parameter_name}"
|
31
31
|
end.join("\n")
|
32
32
|
|
33
|
-
" Some parameters were in the
|
33
|
+
" Some parameters were in the sig, but were not in the parameter list:\n#{details}"
|
34
34
|
end
|
35
35
|
|
36
36
|
def additional_parameters_message_from(additional_parameters)
|
@@ -40,7 +40,7 @@ module Strict
|
|
40
40
|
" - #{parameter_name}"
|
41
41
|
end.join("\n")
|
42
42
|
|
43
|
-
" Some parameters were not in the
|
43
|
+
" Some parameters were not in the sig, but were in the parameter list:\n#{details}"
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/strict/methods/dsl.rb
CHANGED
data/lib/strict/reader/module.rb
CHANGED
@@ -9,7 +9,7 @@ module Strict
|
|
9
9
|
super()
|
10
10
|
|
11
11
|
@configuration = configuration
|
12
|
-
const_set(Strict::Attributes::
|
12
|
+
const_set(Strict::Attributes::Class::CONSTANT, configuration)
|
13
13
|
configuration.attributes.each do |attribute|
|
14
14
|
module_eval(
|
15
15
|
"def #{attribute.name} = #{attribute.instance_variable}", # def name = @instance_variable
|
data/lib/strict/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strict
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-10-
|
11
|
+
date: 2022-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -156,13 +156,20 @@ files:
|
|
156
156
|
- lib/strict/accessor/module.rb
|
157
157
|
- lib/strict/assignment_error.rb
|
158
158
|
- lib/strict/attribute.rb
|
159
|
+
- lib/strict/attributes/class.rb
|
160
|
+
- lib/strict/attributes/coercer.rb
|
159
161
|
- lib/strict/attributes/configuration.rb
|
160
|
-
- lib/strict/attributes/configured.rb
|
161
162
|
- lib/strict/attributes/dsl.rb
|
162
163
|
- lib/strict/attributes/instance.rb
|
164
|
+
- lib/strict/coercers/array.rb
|
165
|
+
- lib/strict/coercers/hash.rb
|
166
|
+
- lib/strict/dsl/coercible.rb
|
163
167
|
- lib/strict/dsl/validatable.rb
|
164
168
|
- lib/strict/error.rb
|
169
|
+
- lib/strict/implementation_does_not_conform_error.rb
|
165
170
|
- lib/strict/initialization_error.rb
|
171
|
+
- lib/strict/interface.rb
|
172
|
+
- lib/strict/interfaces/instance.rb
|
166
173
|
- lib/strict/method.rb
|
167
174
|
- lib/strict/method_call_error.rb
|
168
175
|
- lib/strict/method_definition_error.rb
|