strict 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|