moguro 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moguro
4
+ module Extractor
5
+ end
6
+ end
7
+
8
+ require_relative 'extractors/arguments_extractor'
9
+ require_relative 'extractors/return_value_extractor'
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moguro
4
+ module Handlers
5
+ end
6
+ end
7
+
8
+ require_relative 'handlers/class_handler'
9
+ require_relative 'handlers/method_handler'
@@ -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