bcdd-result 0.10.0 → 0.12.0

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