bcdd-result 0.10.0 → 0.12.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -15
  3. data/README.md +326 -67
  4. data/Rakefile +8 -2
  5. data/lib/bcdd/result/callable_and_then/caller.rb +49 -0
  6. data/lib/bcdd/result/callable_and_then/config.rb +15 -0
  7. data/lib/bcdd/result/callable_and_then/error.rb +11 -0
  8. data/lib/bcdd/result/callable_and_then.rb +9 -0
  9. data/lib/bcdd/result/config/switchers/addons.rb +9 -4
  10. data/lib/bcdd/result/config/switchers/features.rb +5 -1
  11. data/lib/bcdd/result/config.rb +9 -3
  12. data/lib/bcdd/result/context/callable_and_then.rb +39 -0
  13. data/lib/bcdd/result/context/expectations/mixin.rb +11 -3
  14. data/lib/bcdd/result/context/mixin.rb +13 -5
  15. data/lib/bcdd/result/context/success.rb +12 -8
  16. data/lib/bcdd/result/context.rb +34 -16
  17. data/lib/bcdd/result/error.rb +20 -11
  18. data/lib/bcdd/result/expectations/mixin.rb +12 -6
  19. data/lib/bcdd/result/expectations.rb +7 -7
  20. data/lib/bcdd/result/mixin.rb +11 -5
  21. data/lib/bcdd/result/transitions/tracking/disabled.rb +14 -4
  22. data/lib/bcdd/result/transitions/tracking/enabled.rb +38 -18
  23. data/lib/bcdd/result/transitions/tree.rb +10 -6
  24. data/lib/bcdd/result/transitions.rb +8 -10
  25. data/lib/bcdd/result/version.rb +1 -1
  26. data/lib/bcdd/result.rb +38 -23
  27. data/sig/bcdd/result/callable_and_then.rbs +60 -0
  28. data/sig/bcdd/result/config.rbs +3 -0
  29. data/sig/bcdd/result/context.rbs +73 -9
  30. data/sig/bcdd/result/error.rbs +9 -6
  31. data/sig/bcdd/result/expectations.rbs +12 -8
  32. data/sig/bcdd/result/mixin.rbs +10 -2
  33. data/sig/bcdd/result/transitions.rbs +16 -5
  34. data/sig/bcdd/result.rbs +12 -8
  35. metadata +8 -2
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class CallableAndThen::Caller
5
+ def self.call(source, value:, injected_value:, method_name:)
6
+ method = callable_method(source, method_name)
7
+
8
+ Transitions.tracking.record_and_then(method, injected_value, source) do
9
+ result =
10
+ if source.is_a?(::Proc)
11
+ call_proc!(source, value, injected_value)
12
+ else
13
+ call_method!(source, method, value, injected_value)
14
+ end
15
+
16
+ ensure_result_object(source, value, result)
17
+ end
18
+ end
19
+
20
+ def self.call_proc!(source, value, injected_value)
21
+ case source.arity
22
+ when 1 then source.call(value)
23
+ when 2 then source.call(value, injected_value)
24
+ else raise CallableAndThen::Error::InvalidArity.build(source: source, method: :call, arity: '1..2')
25
+ end
26
+ end
27
+
28
+ def self.call_method!(source, method, value, injected_value)
29
+ case method.arity
30
+ when 1 then source.send(method.name, value)
31
+ when 2 then source.send(method.name, value, injected_value)
32
+ else raise CallableAndThen::Error::InvalidArity.build(source: source, method: method.name, arity: '1..2')
33
+ end
34
+ end
35
+
36
+ def self.callable_method(source, method_name)
37
+ source.method(method_name || Config.instance.and_then!.default_method_name_to_call)
38
+ end
39
+
40
+ def self.ensure_result_object(source, _value, result)
41
+ return result if result.is_a?(::BCDD::Result)
42
+
43
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: source)
44
+ end
45
+
46
+ private_class_method :new, :allocate
47
+ private_class_method :call_proc!, :call_method!, :callable_method, :ensure_result_object
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class CallableAndThen::Config
5
+ attr_accessor :default_method_name_to_call
6
+
7
+ def initialize
8
+ self.default_method_name_to_call = :call
9
+ end
10
+
11
+ def options
12
+ { default_method_name_to_call: default_method_name_to_call }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class CallableAndThen::Error < Error
5
+ class InvalidArity < self
6
+ def self.build(source:, method:, arity:)
7
+ new("Invalid arity for #{source.class}##{method} method. Expected arity: #{arity}")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module CallableAndThen
5
+ require_relative 'callable_and_then/error'
6
+ require_relative 'callable_and_then/config'
7
+ require_relative 'callable_and_then/caller'
8
+ end
9
+ end
@@ -3,11 +3,16 @@
3
3
  class BCDD::Result
4
4
  class Config
5
5
  module Addons
6
+ AFFECTS = %w[
7
+ BCDD::Result.mixin
8
+ BCDD::Result::Context.mixin
9
+ BCDD::Result::Expectations.mixin
10
+ BCDD::Result::Context::Expectations.mixin
11
+ ].freeze
12
+
6
13
  OPTIONS = {
7
- continue: {
8
- default: false,
9
- affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
10
- }
14
+ continue: { default: false, affects: AFFECTS },
15
+ given: { default: true, affects: AFFECTS }
11
16
  }.transform_values!(&:freeze).freeze
12
17
 
13
18
  def self.switcher
@@ -10,7 +10,11 @@ class BCDD::Result
10
10
  },
11
11
  transitions: {
12
12
  default: true,
13
- affects: %w[BCDD::Result BCDD::Result::Context]
13
+ affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
14
+ },
15
+ and_then!: {
16
+ default: false,
17
+ affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
14
18
  }
15
19
  }.transform_values!(&:freeze).freeze
16
20
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
-
5
3
  require_relative 'config/options'
6
4
  require_relative 'config/switcher'
7
5
  require_relative 'config/switchers/addons'
@@ -20,6 +18,11 @@ class BCDD::Result
20
18
  @feature = Features.switcher
21
19
  @constant_alias = ConstantAliases.switcher
22
20
  @pattern_matching = PatternMatching.switcher
21
+ @and_then_ = CallableAndThen::Config.new
22
+ end
23
+
24
+ def and_then!
25
+ @and_then_
23
26
  end
24
27
 
25
28
  def freeze
@@ -27,6 +30,7 @@ class BCDD::Result
27
30
  feature.freeze
28
31
  constant_alias.freeze
29
32
  pattern_matching.freeze
33
+ and_then!.freeze
30
34
 
31
35
  super
32
36
  end
@@ -45,7 +49,9 @@ class BCDD::Result
45
49
  end
46
50
 
47
51
  def inspect
48
- "#<#{self.class.name} options=#{options.keys.sort.inspect}>"
52
+ "#<#{self.class.name} " \
53
+ "options=#{options.keys.sort.inspect} " \
54
+ "and_then!=#{and_then!.options.inspect}>"
49
55
  end
50
56
  end
51
57
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ module Context::CallableAndThen
5
+ class Caller < CallableAndThen::Caller
6
+ module KeyArgs
7
+ def self.parameters?(source)
8
+ parameters = source.parameters.map(&:first)
9
+
10
+ !parameters.empty? && parameters.all?(/\Akey/)
11
+ end
12
+
13
+ def self.invalid_arity(source, method)
14
+ CallableAndThen::Error::InvalidArity.build(source: source, method: method, arity: 'only keyword args')
15
+ end
16
+ end
17
+
18
+ def self.call_proc!(source, value, _injected_value)
19
+ return source.call(**value) if KeyArgs.parameters?(source)
20
+
21
+ raise KeyArgs.invalid_arity(source, :call)
22
+ end
23
+
24
+ def self.call_method!(source, method, value, _injected_value)
25
+ return source.send(method.name, **value) if KeyArgs.parameters?(method)
26
+
27
+ raise KeyArgs.invalid_arity(source, method.name)
28
+ end
29
+
30
+ def self.ensure_result_object(source, value, result)
31
+ return result.tap { result.send(:acc).then { _1.merge!(value.merge(_1)) } } if result.is_a?(Context)
32
+
33
+ raise Error::UnexpectedOutcome.build(outcome: result, origin: source, expected: Context::EXPECTED_OUTCOME)
34
+ end
35
+
36
+ private_class_method :call_proc!, :call_method!
37
+ end
38
+ end
39
+ end
@@ -7,13 +7,21 @@ class BCDD::Result::Context
7
7
  Methods = BCDD::Result::Expectations::Mixin::Methods
8
8
 
9
9
  module Addons
10
- module Continuable
10
+ module Continue
11
11
  private def Continue(**value)
12
- Success.new(type: :continued, value: value, subject: self)
12
+ Success.new(type: :continued, value: value, source: self)
13
13
  end
14
14
  end
15
15
 
16
- OPTIONS = { continue: Continuable }.freeze
16
+ module Given
17
+ private def Given(*values)
18
+ value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) }
19
+
20
+ Success.new(type: :given, value: value, source: self)
21
+ end
22
+ end
23
+
24
+ OPTIONS = { continue: Continue, given: Given }.freeze
17
25
 
18
26
  def self.options(config_flags)
19
27
  ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS)
@@ -13,15 +13,15 @@ class BCDD::Result::Context
13
13
  _ResultAs(Failure, type, value)
14
14
  end
15
15
 
16
- private def _ResultAs(kind_class, type, value, halted: nil)
17
- kind_class.new(type: type, value: value, subject: self, halted: halted)
16
+ private def _ResultAs(kind_class, type, value, terminal: nil)
17
+ kind_class.new(type: type, value: value, source: self, terminal: terminal)
18
18
  end
19
19
  end
20
20
 
21
21
  module Addons
22
- module Continuable
22
+ module Continue
23
23
  def Success(type, **value)
24
- _ResultAs(Success, type, value, halted: true)
24
+ _ResultAs(Success, type, value, terminal: true)
25
25
  end
26
26
 
27
27
  private def Continue(**value)
@@ -29,7 +29,15 @@ class BCDD::Result::Context
29
29
  end
30
30
  end
31
31
 
32
- OPTIONS = { continue: Continuable }.freeze
32
+ module Given
33
+ private def Given(*values)
34
+ value = values.map(&:to_h).reduce({}) { |acc, val| acc.merge(val) }
35
+
36
+ _ResultAs(Success, :given, value)
37
+ end
38
+ end
39
+
40
+ OPTIONS = { continue: Continue, given: Given }.freeze
33
41
 
34
42
  def self.options(config_flags)
35
43
  ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS)
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class BCDD::Result::Context::Success < BCDD::Result::Context
4
- include ::BCDD::Result::Success::Methods
3
+ class BCDD::Result
4
+ class Context::Success < Context
5
+ include ::BCDD::Result::Success::Methods
5
6
 
6
- def and_expose(type, keys, halted: true)
7
- unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
8
- raise ::ArgumentError, 'keys must be an Array of Symbols'
9
- end
7
+ def and_expose(type, keys, terminal: true)
8
+ unless keys.is_a?(::Array) && !keys.empty? && keys.all?(::Symbol)
9
+ raise ::ArgumentError, 'keys must be an Array of Symbols'
10
+ end
11
+
12
+ Transitions.tracking.reset_and_then!
10
13
 
11
- exposed_value = acc.merge(value).slice(*keys)
14
+ exposed_value = acc.merge(value).slice(*keys)
12
15
 
13
- self.class.new(type: type, value: exposed_value, subject: subject, halted: halted)
16
+ self.class.new(type: type, value: exposed_value, source: source, terminal: terminal)
17
+ end
14
18
  end
15
19
  end
@@ -6,6 +6,9 @@ class BCDD::Result
6
6
  require_relative 'context/success'
7
7
  require_relative 'context/mixin'
8
8
  require_relative 'context/expectations'
9
+ require_relative 'context/callable_and_then'
10
+
11
+ EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
9
12
 
10
13
  def self.Success(type, **value)
11
14
  Success.new(type: type, value: value)
@@ -15,7 +18,7 @@ class BCDD::Result
15
18
  Failure.new(type: type, value: value)
16
19
  end
17
20
 
18
- def initialize(type:, value:, subject: nil, expectations: nil, halted: nil)
21
+ def initialize(type:, value:, source: nil, expectations: nil, terminal: nil)
19
22
  value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash'
20
23
 
21
24
  @acc = {}
@@ -23,8 +26,16 @@ class BCDD::Result
23
26
  super
24
27
  end
25
28
 
26
- def and_then(method_name = nil, **context_data, &block)
27
- super(method_name, context_data, &block)
29
+ def and_then(method_name = nil, **injected_value, &block)
30
+ super(method_name, injected_value, &block)
31
+ end
32
+
33
+ def and_then!(source, **injected_value)
34
+ _call = injected_value.delete(:_call)
35
+
36
+ acc.merge!(injected_value)
37
+
38
+ super(source, injected_value, _call: _call)
28
39
  end
29
40
 
30
41
  protected
@@ -33,20 +44,23 @@ class BCDD::Result
33
44
 
34
45
  private
35
46
 
36
- SubjectMethodArity = ->(method) do
47
+ SourceMethodArity = ->(method) do
37
48
  return 0 if method.arity.zero?
38
- return 1 if method.parameters.map(&:first).all?(/\Akey/)
49
+
50
+ parameters = method.parameters.map(&:first)
51
+
52
+ return 1 if !parameters.empty? && parameters.all?(/\Akey/)
39
53
 
40
54
  -1
41
55
  end
42
56
 
43
- def call_and_then_subject_method!(method, context_data)
44
- acc.merge!(value.merge(context_data))
57
+ def call_and_then_source_method!(method, injected_value)
58
+ acc.merge!(value.merge(injected_value))
45
59
 
46
- case SubjectMethodArity[method]
47
- when 0 then subject.send(method.name)
48
- when 1 then subject.send(method.name, **acc)
49
- else raise Error::InvalidSubjectMethodArity.build(subject: subject, method: method, max_arity: 1)
60
+ case SourceMethodArity[method]
61
+ when 0 then source.send(method.name)
62
+ when 1 then source.send(method.name, **acc)
63
+ else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1)
50
64
  end
51
65
  end
52
66
 
@@ -56,20 +70,24 @@ class BCDD::Result
56
70
  block.call(acc)
57
71
  end
58
72
 
73
+ def call_and_then_callable!(source, value:, injected_value:, method_name:)
74
+ acc.merge!(value.merge(injected_value))
75
+
76
+ CallableAndThen::Caller.call(source, value: acc, injected_value: injected_value, method_name: method_name)
77
+ end
78
+
59
79
  def ensure_result_object(result, origin:)
60
80
  raise_unexpected_outcome_error(result, origin) unless result.is_a?(Context)
61
81
 
62
- return result.tap { _1.acc.merge!(acc) } if result.subject.equal?(subject)
82
+ return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source)
63
83
 
64
- raise Error::InvalidResultSubject.build(given_result: result, expected_subject: subject)
84
+ raise Error::InvalidResultSource.build(given_result: result, expected_source: source)
65
85
  end
66
86
 
67
- EXPECTED_OUTCOME = 'BCDD::Result::Context::Success or BCDD::Result::Context::Failure'
68
-
69
87
  def raise_unexpected_outcome_error(result, origin)
70
88
  raise Error::UnexpectedOutcome.build(outcome: result, origin: origin, expected: EXPECTED_OUTCOME)
71
89
  end
72
90
 
73
- private_constant :SubjectMethodArity, :EXPECTED_OUTCOME
91
+ private_constant :SourceMethodArity
74
92
  end
75
93
  end
@@ -9,7 +9,7 @@ class BCDD::Result::Error < StandardError
9
9
  end
10
10
 
11
11
  class MissingTypeArgument < self
12
- def initialize(_arg = nil)
12
+ def initialize(_message = nil)
13
13
  super('A type (argument) is required to invoke the #on/#on_type method')
14
14
  end
15
15
  end
@@ -22,29 +22,38 @@ class BCDD::Result::Error < StandardError
22
22
  end
23
23
  end
24
24
 
25
- class InvalidResultSubject < self
26
- def self.build(given_result:, expected_subject:)
25
+ class InvalidResultSource < self
26
+ def self.build(given_result:, expected_source:)
27
27
  message =
28
- "You cannot call #and_then and return a result that does not belong to the subject!\n" \
29
- "Expected subject: #{expected_subject.inspect}\n" \
30
- "Given subject: #{given_result.send(:subject).inspect}\n" \
28
+ "You cannot call #and_then and return a result that does not belong to the same source!\n" \
29
+ "Expected source: #{expected_source.inspect}\n" \
30
+ "Given source: #{given_result.send(:source).inspect}\n" \
31
31
  "Given result: #{given_result.inspect}"
32
32
 
33
33
  new(message)
34
34
  end
35
35
  end
36
36
 
37
- class InvalidSubjectMethodArity < self
38
- def self.build(subject:, method:, max_arity:)
39
- new("#{subject.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
37
+ class InvalidSourceMethodArity < self
38
+ def self.build(source:, method:, max_arity:)
39
+ new("#{source.class}##{method.name} has unsupported arity (#{method.arity}). Expected 0..#{max_arity}")
40
40
  end
41
41
  end
42
42
 
43
43
  class UnhandledTypes < self
44
44
  def self.build(types:)
45
- subject = types.size == 1 ? 'This was' : 'These were'
45
+ source = types.size == 1 ? 'This was' : 'These were'
46
46
 
47
- new("You must handle all cases. #{subject} not handled: #{types.map(&:inspect).join(', ')}")
47
+ new("You must handle all cases. #{source} not handled: #{types.map(&:inspect).join(', ')}")
48
+ end
49
+ end
50
+
51
+ class CallableAndThenDisabled < self
52
+ def initialize(_message = nil)
53
+ super(
54
+ 'You cannot use #and_then! as the feature is disabled. ' \
55
+ 'Please use BCDD::Result.config.feature.enable!(:and_then!) to enable it.'
56
+ )
48
57
  end
49
58
  end
50
59
  end
@@ -24,25 +24,31 @@ class BCDD::Result
24
24
 
25
25
  FACTORY = <<~RUBY
26
26
  private def _Result
27
- @_Result ||= Result.with(subject: self, halted: %<halted>s)
27
+ @_Result ||= Result.with(source: self, terminal: %<terminal>s)
28
28
  end
29
29
  RUBY
30
30
 
31
31
  def self.to_eval(addons)
32
- halted = addons.key?(:continue) ? 'true' : 'nil'
32
+ terminal = addons.key?(:continue) ? 'true' : 'nil'
33
33
 
34
- "#{BASE}\n#{format(FACTORY, halted: halted)}"
34
+ "#{BASE}\n#{format(FACTORY, terminal: terminal)}"
35
35
  end
36
36
  end
37
37
 
38
38
  module Addons
39
- module Continuable
39
+ module Continue
40
40
  private def Continue(value)
41
- Success.new(type: :continued, value: value, subject: self)
41
+ Success.new(type: :continued, value: value, source: self)
42
42
  end
43
43
  end
44
44
 
45
- OPTIONS = { continue: Continuable }.freeze
45
+ module Given
46
+ private def Given(value)
47
+ Success.new(type: :given, value: value, source: self)
48
+ end
49
+ end
50
+
51
+ OPTIONS = { continue: Continue, given: Given }.freeze
46
52
 
47
53
  def self.options(config_flags)
48
54
  Config::Options.addon(map: config_flags, from: OPTIONS)
@@ -38,10 +38,10 @@ class BCDD::Result
38
38
 
39
39
  private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
40
40
 
41
- def initialize(subject: nil, contract: nil, halted: nil, **options)
42
- @halted = halted
41
+ def initialize(source: nil, contract: nil, terminal: nil, **options)
42
+ @terminal = terminal
43
43
 
44
- @subject = subject
44
+ @source = source
45
45
 
46
46
  @contract = contract if contract.is_a?(Contract::Evaluator)
47
47
 
@@ -60,16 +60,16 @@ class BCDD::Result
60
60
  _ResultAs(Failure, type, value)
61
61
  end
62
62
 
63
- def with(subject:, halted: nil)
64
- self.class.new(subject: subject, halted: halted, contract: contract)
63
+ def with(source:, terminal: nil)
64
+ self.class.new(source: source, terminal: terminal, contract: contract)
65
65
  end
66
66
 
67
67
  private
68
68
 
69
69
  def _ResultAs(kind_class, type, value)
70
- kind_class.new(type: type, value: value, subject: subject, expectations: contract, halted: halted)
70
+ kind_class.new(type: type, value: value, source: source, expectations: contract, terminal: terminal)
71
71
  end
72
72
 
73
- attr_reader :subject, :halted, :contract
73
+ attr_reader :source, :terminal, :contract
74
74
  end
75
75
  end
@@ -20,15 +20,15 @@ class BCDD::Result
20
20
  _ResultAs(Failure, type, value)
21
21
  end
22
22
 
23
- private def _ResultAs(kind_class, type, value, halted: nil)
24
- kind_class.new(type: type, value: value, subject: self, halted: halted)
23
+ private def _ResultAs(kind_class, type, value, terminal: nil)
24
+ kind_class.new(type: type, value: value, source: self, terminal: terminal)
25
25
  end
26
26
  end
27
27
 
28
28
  module Addons
29
- module Continuable
29
+ module Continue
30
30
  def Success(type, value = nil)
31
- _ResultAs(Success, type, value, halted: true)
31
+ _ResultAs(Success, type, value, terminal: true)
32
32
  end
33
33
 
34
34
  private def Continue(value)
@@ -36,7 +36,13 @@ class BCDD::Result
36
36
  end
37
37
  end
38
38
 
39
- OPTIONS = { continue: Continuable }.freeze
39
+ module Given
40
+ private def Given(value)
41
+ _ResultAs(Success, :given, value)
42
+ end
43
+ end
44
+
45
+ OPTIONS = { continue: Continue, given: Given }.freeze
40
46
 
41
47
  def self.options(config_flags)
42
48
  Config::Options.addon(map: config_flags, from: OPTIONS)
@@ -2,16 +2,26 @@
2
2
 
3
3
  module BCDD::Result::Transitions
4
4
  module Tracking::Disabled
5
- def self.start(name:, desc:); end
6
-
7
- def self.finish(result:); end
5
+ def self.exec(_name, _desc)
6
+ EnsureResult[yield]
7
+ end
8
8
 
9
9
  def self.reset!; end
10
10
 
11
11
  def self.record(result); end
12
12
 
13
- def self.record_and_then(_type, _data, _subject)
13
+ def self.record_and_then(_type, _data, _source)
14
14
  yield
15
15
  end
16
+
17
+ def self.reset_and_then!; end
18
+
19
+ class << self
20
+ private
21
+
22
+ def start(name, desc); end
23
+
24
+ def finish(result); end
25
+ end
16
26
  end
17
27
  end