moguro 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.
@@ -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