moguro 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +48 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +12 -0
- data/example.rb +64 -0
- data/lib/moguro.rb +33 -0
- data/lib/moguro/caluse.rb +90 -0
- data/lib/moguro/contract.rb +67 -0
- data/lib/moguro/decorator.rb +69 -0
- data/lib/moguro/errors.rb +97 -0
- data/lib/moguro/extractor.rb +9 -0
- data/lib/moguro/extractors/arguments_extractor.rb +36 -0
- data/lib/moguro/extractors/return_value_extractor.rb +19 -0
- data/lib/moguro/handler.rb +9 -0
- data/lib/moguro/handlers/class_handler.rb +71 -0
- data/lib/moguro/handlers/method_handler.rb +74 -0
- data/lib/moguro/method_reference.rb +45 -0
- data/lib/moguro/processors/arguments_processor.rb +42 -0
- data/lib/moguro/processors/contract_processor.rb +66 -0
- data/lib/moguro/processors/enumerable_processor.rb +43 -0
- data/lib/moguro/processros.rb +10 -0
- data/lib/moguro/sandbox.rb +15 -0
- data/lib/moguro/types.rb +37 -0
- data/lib/moguro/types/any.rb +23 -0
- data/lib/moguro/types/boolean.rb +29 -0
- data/lib/moguro/types/enumerable.rb +47 -0
- data/lib/moguro/types/nil.rb +18 -0
- data/lib/moguro/types/skin.rb +29 -0
- data/lib/moguro/values.rb +72 -0
- data/lib/moguro/version.rb +5 -0
- data/moguro.gemspec +37 -0
- metadata +263 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
###
|
5
|
+
# Contract of class state
|
6
|
+
# @since 0.0.1
|
7
|
+
# @abstract
|
8
|
+
###
|
9
|
+
class Contract
|
10
|
+
attr_reader :method
|
11
|
+
|
12
|
+
def initialize(klass, callback, method)
|
13
|
+
@klass = klass
|
14
|
+
@type_clauses = Moguro::Processors::ContractProcessor.generate_type_validator(callback, klass)
|
15
|
+
@cb = callback
|
16
|
+
@method = method
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify!(instance, args)
|
20
|
+
values = extractor.extract(args)
|
21
|
+
@type_clauses.verify!(values)
|
22
|
+
instance.instance_exec(values.to_h, &@cb)
|
23
|
+
end
|
24
|
+
|
25
|
+
def extractor
|
26
|
+
raise NotImplementedError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class PreconditionContract < Contract
|
31
|
+
def initialize(klass, callback, method)
|
32
|
+
super(klass, callback, method)
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify!(instance, args)
|
36
|
+
super
|
37
|
+
rescue Moguro::Errors::TypeMismatchError => e
|
38
|
+
raise Moguro::Errors::ArgumentsTypeMismatchError.new(e, @klass, method)
|
39
|
+
rescue => e
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
|
43
|
+
def extractor
|
44
|
+
@extractor ||= Moguro::Extractor::ArgumentsExtractor.new(method.parameters)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class PostconditionContract < Contract
|
49
|
+
def initialize(klass, callback, method)
|
50
|
+
super(klass, callback, method)
|
51
|
+
end
|
52
|
+
|
53
|
+
def verify!(instance, args)
|
54
|
+
super
|
55
|
+
rescue Moguro::Errors::TypeMismatchError => e
|
56
|
+
raise Moguro::Errors::ReturnValueTypeMismatchError.new(e, @klass, method)
|
57
|
+
rescue => e
|
58
|
+
raise e
|
59
|
+
end
|
60
|
+
|
61
|
+
def extractor
|
62
|
+
@extractor ||= Moguro::Extractor::ReturnValueExtractor.new(
|
63
|
+
@cb.parameters
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
###
|
5
|
+
# Provide contract programming feature to the class that included this module.
|
6
|
+
# Validates the arguments to the method, and validates the return value of the method.
|
7
|
+
# And you arbitrary test possible by Assertion library.
|
8
|
+
# @since 0.0.1
|
9
|
+
###
|
10
|
+
module Decorator
|
11
|
+
CONTRACT_CONTAINER = Struct.new(:pre, :post)
|
12
|
+
|
13
|
+
def self.included(klass)
|
14
|
+
klass.extend(ClassMethods)
|
15
|
+
klass.include(Moguro::Types)
|
16
|
+
# @todo
|
17
|
+
return unless Moguro.enabled?
|
18
|
+
require 'test/unit/assertions'
|
19
|
+
klass.include(Test::Unit::Assertions)
|
20
|
+
klass.extend(Test::Unit::Assertions)
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def singleton_method_added(name)
|
25
|
+
return super unless Moguro.enabled? && contract_handler.validatable?
|
26
|
+
super
|
27
|
+
contract_handler.handle_singleton_method(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_added(name)
|
31
|
+
return super unless Moguro.enabled? && contract_handler.validatable?
|
32
|
+
super
|
33
|
+
contract_handler.handle_method(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
###
|
37
|
+
# Validates the arguments to the method
|
38
|
+
# @since 0.0.1
|
39
|
+
# @param [Lambda|Proc] callback
|
40
|
+
# @return [Void]
|
41
|
+
#
|
42
|
+
def pre_c(callback)
|
43
|
+
return unless Moguro.enabled?
|
44
|
+
contract_handler.contract_arguments(callback)
|
45
|
+
end
|
46
|
+
|
47
|
+
###
|
48
|
+
# Validates the arguments to the method
|
49
|
+
# @since 0.0.1
|
50
|
+
# @param [Lambda|Proc] callback
|
51
|
+
# @return [Void]
|
52
|
+
###
|
53
|
+
def post_c(callback)
|
54
|
+
return unless Moguro.enabled?
|
55
|
+
contract_handler.contract_return_value(callback)
|
56
|
+
end
|
57
|
+
|
58
|
+
###
|
59
|
+
# Validates the arguments to the method
|
60
|
+
# @since 0.0.1
|
61
|
+
# @param [Lambda|Proc] callback
|
62
|
+
# @return [Void]
|
63
|
+
##
|
64
|
+
def contract_handler
|
65
|
+
@contract_handler ||= Moguro::Handlers::ClassHandler.new(self)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
module Errors
|
5
|
+
###
|
6
|
+
# @private
|
7
|
+
# Base class for Errors
|
8
|
+
# If default failure callback is used it stores failure data
|
9
|
+
###
|
10
|
+
class BaseError < ::ArgumentError
|
11
|
+
end
|
12
|
+
|
13
|
+
###
|
14
|
+
# Type miss match error class for Arguments and return value
|
15
|
+
# @attribute :actual, :expected
|
16
|
+
class TypeMismatchError < BaseError
|
17
|
+
attr_accessor :actual, :expected
|
18
|
+
attr_reader :failure_clause, :failure_value
|
19
|
+
|
20
|
+
def initialize(failure_clause, failure_value)
|
21
|
+
@failure_clause = failure_clause
|
22
|
+
@failure_value = failure_value
|
23
|
+
@actual = nil
|
24
|
+
@expected = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def failure_detail
|
28
|
+
<<~"DETAIL".chomp
|
29
|
+
Expected: #{@expected}
|
30
|
+
Actual: #{@actual}
|
31
|
+
DETAIL
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure_summary
|
35
|
+
"Type MissMatch: #{@failure_clause.key} is expected #{@failure_clause.type_inspect} actual #{@failure_value.type_inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ArgumentsTypeMismatchError < BaseError
|
40
|
+
def initialize(type_mismatch_error, klass, method)
|
41
|
+
@e = type_mismatch_error
|
42
|
+
@klass = klass
|
43
|
+
@method = method
|
44
|
+
super(failure_message)
|
45
|
+
end
|
46
|
+
|
47
|
+
def failure_title
|
48
|
+
'ArgumentsTypeError'
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_message
|
52
|
+
<<~"MESSAGE".chomp
|
53
|
+
#{failure_title} => #{@e.failure_summary}
|
54
|
+
#{@e.failure_detail}
|
55
|
+
#{failure_location}
|
56
|
+
MESSAGE
|
57
|
+
end
|
58
|
+
|
59
|
+
def failure_location
|
60
|
+
<<~"LOCATION".chomp
|
61
|
+
Value guarded in: #{@klass}::#{@method.name}
|
62
|
+
At: #{@method.position}
|
63
|
+
LOCATION
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ReturnValueTypeMismatchError < BaseError
|
68
|
+
def initialize(type_mismatch_error, klass, method)
|
69
|
+
@e = type_mismatch_error
|
70
|
+
@klass = klass
|
71
|
+
@method = method
|
72
|
+
super(failure_message)
|
73
|
+
end
|
74
|
+
|
75
|
+
def failure_title
|
76
|
+
'ReturnValueTypeError'
|
77
|
+
end
|
78
|
+
|
79
|
+
def failure_message
|
80
|
+
<<~"MESSAGE".chomp
|
81
|
+
#{failure_title} => #{@e.failure_summary}
|
82
|
+
#{@e.failure_detail}
|
83
|
+
#{failure_location}
|
84
|
+
MESSAGE
|
85
|
+
end
|
86
|
+
|
87
|
+
def failure_location
|
88
|
+
<<~"LOCATION".chomp
|
89
|
+
Value guarded in: #{@klass}::#{@method.name}
|
90
|
+
At: #{@method.position}
|
91
|
+
LOCATION
|
92
|
+
end
|
93
|
+
end
|
94
|
+
class ArgumentError < BaseError
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
module Extractor
|
5
|
+
class ArgumentsExtractor
|
6
|
+
def initialize(parameters)
|
7
|
+
@parameters = parameters
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract(args)
|
11
|
+
opt = args.last.is_a?(Hash) ? args.last : nil
|
12
|
+
@parameters.each_with_object(Values.new).with_index do |(params, values), i|
|
13
|
+
arg_type = params[0]
|
14
|
+
key = params[1]
|
15
|
+
case arg_type
|
16
|
+
when :req
|
17
|
+
val = args[i]
|
18
|
+
values.add_value(key, val)
|
19
|
+
when :keyreq
|
20
|
+
if opt&.key?(key)
|
21
|
+
val = opt[key]
|
22
|
+
values.add_value(key, val)
|
23
|
+
else
|
24
|
+
values.add_value(key, val, missing: true)
|
25
|
+
end
|
26
|
+
when :key
|
27
|
+
val = opt[key]
|
28
|
+
values.add_value(key, val)
|
29
|
+
when :opt
|
30
|
+
values.add_value(key, args[i])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
module Extractor
|
5
|
+
class ReturnValueExtractor
|
6
|
+
def initialize(parameters)
|
7
|
+
@parameters = parameters
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract(return_value)
|
11
|
+
@parameters.each_with_object(Values.new).with_index do |(params, values), i|
|
12
|
+
key = params[1]
|
13
|
+
val = return_value[i]
|
14
|
+
values.add_value(key, val, missing: val.nil?)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
module Handlers
|
5
|
+
# Handler for method added event
|
6
|
+
# @private
|
7
|
+
# @since 0.0.1
|
8
|
+
# @attr_reader [Class] binding class
|
9
|
+
class ClassHandler
|
10
|
+
attr_reader :klass
|
11
|
+
|
12
|
+
def initialize(klass)
|
13
|
+
@klass = klass
|
14
|
+
@instance_handlers = {}
|
15
|
+
@class_handlers = {}
|
16
|
+
@arguments_contract = nil
|
17
|
+
@return_value_contract = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_singleton_method(name)
|
21
|
+
@class_handlers[name] ||= SingletonMethodHandler.new(
|
22
|
+
self,
|
23
|
+
name,
|
24
|
+
arguments_contract: @arguments_contract,
|
25
|
+
return_value_contract: @return_value_contract
|
26
|
+
)
|
27
|
+
clear_validator
|
28
|
+
m = @class_handlers[name].module
|
29
|
+
@klass.singleton_class.class_eval { prepend(m) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle_method(name)
|
33
|
+
@instance_handlers[name] = MethodHandler.new(
|
34
|
+
self,
|
35
|
+
name,
|
36
|
+
arguments_contract: @arguments_contract,
|
37
|
+
return_value_contract: @return_value_contract
|
38
|
+
)
|
39
|
+
clear_validator
|
40
|
+
@klass.prepend(@instance_handlers[name].module)
|
41
|
+
end
|
42
|
+
|
43
|
+
def contract_arguments(callback)
|
44
|
+
@arguments_contract = callback
|
45
|
+
end
|
46
|
+
|
47
|
+
def contract_return_value(callback)
|
48
|
+
@return_value_contract = callback
|
49
|
+
end
|
50
|
+
|
51
|
+
def validatable?
|
52
|
+
!@arguments_contract.nil? || !@return_value_contract.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def _get_class_method_handler(name)
|
56
|
+
@class_handlers[name]
|
57
|
+
end
|
58
|
+
|
59
|
+
def _get_instance_method_handler(name)
|
60
|
+
@instance_handlers[name]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def clear_validator
|
66
|
+
@arguments_contract = nil
|
67
|
+
@return_value_contract = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Moguro
|
4
|
+
module Handlers
|
5
|
+
class MethodHandler
|
6
|
+
def initialize(class_handler, name, arguments_contract: nil, return_value_contract: nil)
|
7
|
+
@class_handler = class_handler
|
8
|
+
@name = name
|
9
|
+
@arguments_contract =
|
10
|
+
if arguments_contract.nil?
|
11
|
+
arguments_contract
|
12
|
+
else
|
13
|
+
PreconditionContract.new(klass, arguments_contract, reference)
|
14
|
+
end
|
15
|
+
@return_value_contract =
|
16
|
+
if return_value_contract.nil?
|
17
|
+
return_value_contract
|
18
|
+
else
|
19
|
+
PostconditionContract.new(klass, return_value_contract, reference)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def module
|
24
|
+
m = Module.new
|
25
|
+
|
26
|
+
arguments_contract = @arguments_contract
|
27
|
+
return_value_contract = @return_value_contract
|
28
|
+
|
29
|
+
arguments_contract_cb = lambda { |klass, args|
|
30
|
+
arguments_contract&.verify!(klass, args)
|
31
|
+
}
|
32
|
+
|
33
|
+
return_value_contract_cb = lambda { |klass, results|
|
34
|
+
return_value_contract&.verify!(klass, results)
|
35
|
+
}
|
36
|
+
name = @name
|
37
|
+
m.module_eval do
|
38
|
+
define_method(name) do |*args, &block|
|
39
|
+
arguments_contract_cb.call(self, args)
|
40
|
+
*results = super(*args, &block)
|
41
|
+
return_value_contract_cb.call(self, results)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
m
|
45
|
+
end
|
46
|
+
|
47
|
+
def reference
|
48
|
+
@reference ||= MethodReference.new(
|
49
|
+
method
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def parameters
|
56
|
+
@parameters ||= method.parameters
|
57
|
+
end
|
58
|
+
|
59
|
+
def method
|
60
|
+
@method ||= klass.instance_method(@name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def klass
|
64
|
+
@klass ||= @class_handler.klass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class SingletonMethodHandler < MethodHandler
|
69
|
+
def method
|
70
|
+
@method ||= klass.method(@name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|