bcdd-result 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -3,10 +3,16 @@
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rake/testtask'
5
5
 
6
+ Rake::TestTask.new(:test_configuration) do |t|
7
+ t.libs += %w[lib test]
8
+
9
+ t.test_files = FileList.new('test/**/configuration_test.rb')
10
+ end
11
+
6
12
  Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/*_test.rb']
13
+ t.libs += %w[lib test]
14
+
15
+ t.test_files = FileList.new('test/**/*_test.rb')
10
16
  end
11
17
 
12
18
  require 'rubocop/rake_task'
data/Steepfile CHANGED
@@ -10,7 +10,7 @@ target :lib do
10
10
  # check 'app/models/**/*.rb' # Glob
11
11
  # ignore 'lib/templates/*.rb'
12
12
 
13
- # library 'pathname' # Standard libraries
13
+ library 'singleton' # Standard libraries
14
14
  # library 'strong_json' # Gems
15
15
 
16
16
  # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ module ConstantAlias
6
+ RESULT = 'Result'
7
+
8
+ OPTIONS = {
9
+ RESULT => { default: false, affects: %w[Object] }
10
+ }.transform_values!(&:freeze).freeze
11
+
12
+ MAPPING = {
13
+ RESULT => { target: ::Object, name: :Result, value: ::BCDD::Result }
14
+ }.transform_values!(&:freeze).freeze
15
+
16
+ Listener = ->(option_name, boolean) do
17
+ mapping = MAPPING.fetch(option_name)
18
+
19
+ target, name, value = mapping.fetch_values(:target, :name, :value)
20
+
21
+ defined = target.const_defined?(name, false)
22
+
23
+ boolean ? defined || target.const_set(name, value) : defined && target.send(:remove_const, name)
24
+ end
25
+
26
+ def self.switcher
27
+ Switcher.new(options: OPTIONS, listener: Listener)
28
+ end
29
+ end
30
+
31
+ private_constant :ConstantAlias
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ module Options
6
+ def self.with_defaults(all_flags, config)
7
+ all_flags ||= {}
8
+
9
+ default_flags = Config.instance.to_h.fetch(config)
10
+
11
+ config_flags = all_flags.fetch(config, {})
12
+
13
+ default_flags.merge(config_flags).slice(*default_flags.keys)
14
+ end
15
+
16
+ def self.filter_map(all_flags, config:, from:)
17
+ with_defaults(all_flags, config)
18
+ .filter_map { |name, truthy| from[name] if truthy }
19
+ end
20
+
21
+ def self.addon(map:, from:)
22
+ filter_map(map, config: :addon, from: from)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BCDD::Result
4
+ class Config
5
+ class Switcher
6
+ attr_reader :_options, :_affects, :listener
7
+
8
+ private :_options, :_affects, :listener
9
+
10
+ def initialize(options:, listener: nil)
11
+ @_options = options.transform_values { _1.fetch(:default) }
12
+ @_affects = options.transform_values { _1.fetch(:affects) }
13
+ @listener = listener
14
+ end
15
+
16
+ def inspect
17
+ "#<#{self.class.name} options=#{_options.inspect}>"
18
+ end
19
+
20
+ def freeze
21
+ _options.freeze
22
+ super
23
+ end
24
+
25
+ def to_h
26
+ _options.dup
27
+ end
28
+
29
+ def options
30
+ _affects.to_h { |name, affects| [name, { enabled: _options[name], affects: affects }] }
31
+ end
32
+
33
+ def enabled?(name)
34
+ _options[name] || false
35
+ end
36
+
37
+ def enable!(*names)
38
+ set_many(names, to: true)
39
+ end
40
+
41
+ def disable!(*names)
42
+ set_many(names, to: false)
43
+ end
44
+
45
+ private
46
+
47
+ def set_many(names, to:)
48
+ require_option!(names)
49
+
50
+ names.each do |name|
51
+ set_one(name, to)
52
+
53
+ listener&.call(name, to)
54
+ end
55
+
56
+ options.slice(*names)
57
+ end
58
+
59
+ def set_one(name, boolean)
60
+ validate_option!(name)
61
+
62
+ _options[name] = boolean
63
+ end
64
+
65
+ def require_option!(names)
66
+ raise ::ArgumentError, "One or more options required. #{available_options_message}" if names.empty?
67
+ end
68
+
69
+ def validate_option!(name)
70
+ return if _options.key?(name)
71
+
72
+ raise ::ArgumentError, "Invalid option: #{name.inspect}. #{available_options_message}"
73
+ end
74
+
75
+ def available_options_message
76
+ "Available options: #{_options.keys.map(&:inspect).join(', ')}"
77
+ end
78
+ end
79
+
80
+ private_constant :Switcher
81
+ end
82
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ require_relative 'config/options'
6
+ require_relative 'config/switcher'
7
+ require_relative 'config/constant_alias'
8
+
9
+ class BCDD::Result
10
+ class Config
11
+ include Singleton
12
+
13
+ ADDON = {
14
+ continue: {
15
+ default: false,
16
+ affects: %w[BCDD::Result BCDD::Result::Context BCDD::Result::Expectations BCDD::Result::Context::Expectations]
17
+ }
18
+ }.transform_values!(&:freeze).freeze
19
+
20
+ FEATURE = {
21
+ expectations: {
22
+ default: true,
23
+ affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
24
+ }
25
+ }.transform_values!(&:freeze).freeze
26
+
27
+ PATTERN_MATCHING = {
28
+ nil_as_valid_value_checking: {
29
+ default: false,
30
+ affects: %w[BCDD::Result::Expectations BCDD::Result::Context::Expectations]
31
+ }
32
+ }.transform_values!(&:freeze).freeze
33
+
34
+ attr_reader :addon, :feature, :constant_alias, :pattern_matching
35
+
36
+ def initialize
37
+ @addon = Switcher.new(options: ADDON)
38
+ @feature = Switcher.new(options: FEATURE)
39
+ @constant_alias = ConstantAlias.switcher
40
+ @pattern_matching = Switcher.new(options: PATTERN_MATCHING)
41
+ end
42
+
43
+ def freeze
44
+ addon.freeze
45
+ feature.freeze
46
+ constant_alias.freeze
47
+ pattern_matching.freeze
48
+
49
+ super
50
+ end
51
+
52
+ def options
53
+ {
54
+ addon: addon,
55
+ feature: feature,
56
+ constant_alias: constant_alias,
57
+ pattern_matching: pattern_matching
58
+ }
59
+ end
60
+
61
+ def to_h
62
+ options.transform_values(&:to_h)
63
+ end
64
+
65
+ def inspect
66
+ "#<#{self.class.name} options=#{options.keys.sort.inspect}>"
67
+ end
68
+
69
+ private_constant :ADDON, :FEATURE, :PATTERN_MATCHING
70
+ end
71
+ end
@@ -2,21 +2,9 @@
2
2
 
3
3
  class BCDD::Result::Context
4
4
  module Expectations::Mixin
5
- METHODS = <<~RUBY
6
- def Success(...)
7
- _Result::Success(...)
8
- end
9
-
10
- def Failure(...)
11
- _Result::Failure(...)
12
- end
5
+ Factory = BCDD::Result::Expectations::Mixin::Factory
13
6
 
14
- private
15
-
16
- def _Result
17
- @_Result ||= Result.with(subject: self)
18
- end
19
- RUBY
7
+ METHODS = BCDD::Result::Expectations::Mixin::METHODS
20
8
 
21
9
  module Addons
22
10
  module Continuable
@@ -25,10 +13,10 @@ class BCDD::Result::Context
25
13
  end
26
14
  end
27
15
 
28
- OPTIONS = { Continue: Continuable }.freeze
16
+ OPTIONS = { continue: Continuable }.freeze
29
17
 
30
- def self.options(names)
31
- Array(names).filter_map { |name| OPTIONS[name] }
18
+ def self.options(config_flags)
19
+ ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS)
32
20
  end
33
21
  end
34
22
  end
@@ -1,27 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class BCDD::Result::Context
4
- class Expectations
4
+ class Expectations < BCDD::Result::Expectations
5
5
  require_relative 'expectations/mixin'
6
6
 
7
- def self.mixin(success: nil, failure: nil, with: nil)
8
- addons = Mixin::Addons.options(with)
9
-
10
- mod = ::BCDD::Result::Expectations::Mixin.module!
11
- mod.const_set(:Result, new(success: success, failure: failure).freeze)
12
- mod.module_eval(Mixin::METHODS)
13
- mod.send(:include, *addons) unless addons.empty?
14
- mod
7
+ def self.mixin_module
8
+ Mixin
15
9
  end
16
10
 
17
- def initialize(subject: nil, success: nil, failure: nil, contract: nil)
18
- @subject = subject
19
-
20
- @contract = contract if contract.is_a?(::BCDD::Result::Contract::Evaluator)
21
-
22
- @contract ||= ::BCDD::Result::Contract.new(success: success, failure: failure).freeze
11
+ def self.result_factory_without_expectations
12
+ ::BCDD::Result::Context
23
13
  end
24
14
 
15
+ private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
16
+
25
17
  def Success(type, **value)
26
18
  Success.new(type: type, value: value, subject: subject, expectations: contract)
27
19
  end
@@ -29,13 +21,5 @@ class BCDD::Result::Context
29
21
  def Failure(type, **value)
30
22
  Failure.new(type: type, value: value, subject: subject, expectations: contract)
31
23
  end
32
-
33
- def with(subject:)
34
- self.class.new(subject: subject, contract: contract)
35
- end
36
-
37
- private
38
-
39
- attr_reader :subject, :contract
40
24
  end
41
25
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  class BCDD::Result::Context
4
4
  module Mixin
5
+ Factory = BCDD::Result::Mixin::Factory
6
+
5
7
  module Methods
6
8
  def Success(type, **value)
7
9
  Success.new(type: type, value: value, subject: self)
@@ -19,20 +21,21 @@ class BCDD::Result::Context
19
21
  end
20
22
  end
21
23
 
22
- OPTIONS = { Continue: Continuable }.freeze
24
+ OPTIONS = { continue: Continuable }.freeze
23
25
 
24
- def self.options(names)
25
- Array(names).filter_map { |name| OPTIONS[name] }
26
+ def self.options(config_flags)
27
+ ::BCDD::Result::Config::Options.addon(map: config_flags, from: OPTIONS)
26
28
  end
27
29
  end
28
30
  end
29
31
 
30
- def self.mixin(with: nil)
31
- addons = Mixin::Addons.options(with)
32
+ def self.mixin_module
33
+ Mixin
34
+ end
32
35
 
33
- mod = ::BCDD::Result::Mixin.module!
34
- mod.send(:include, Mixin::Methods)
35
- mod.send(:include, *addons) unless addons.empty?
36
- mod
36
+ def self.result_factory
37
+ ::BCDD::Result::Context
37
38
  end
39
+
40
+ private_class_method :mixin_module, :result_factory
38
41
  end
@@ -4,7 +4,12 @@ class BCDD::Result
4
4
  class Contract::ForTypesAndValues
5
5
  include Contract::Interface
6
6
 
7
- def initialize(types_and_values)
7
+ def initialize(types_and_values, config)
8
+ @nil_as_valid_value_checking =
9
+ Config::Options
10
+ .with_defaults(config, :pattern_matching)
11
+ .fetch(:nil_as_valid_value_checking)
12
+
8
13
  @types_and_values = types_and_values.transform_keys(&:to_sym)
9
14
 
10
15
  @types_contract = Contract::ForTypes.new(@types_and_values.keys)
@@ -29,7 +34,7 @@ class BCDD::Result
29
34
 
30
35
  checking_result = value_checking === value
31
36
 
32
- return value if checking_result || (Contract.nil_as_valid_value_checking? && checking_result.nil?)
37
+ return value if checking_result || (checking_result.nil? && @nil_as_valid_value_checking)
33
38
 
34
39
  raise Contract::Error::UnexpectedValue.build(type: type, value: value)
35
40
  rescue ::NoMatchingPatternError => e
@@ -19,24 +19,14 @@ module BCDD::Result::Contract
19
19
  TypeChecker.new(data.type, expectations: contract)
20
20
  end
21
21
 
22
- ToEnsure = ->(spec) do
22
+ ToEnsure = ->(spec, config) do
23
23
  return Disabled if spec.nil?
24
24
 
25
- spec.is_a?(::Hash) ? ForTypesAndValues.new(spec) : ForTypes.new(Array(spec))
25
+ spec.is_a?(::Hash) ? ForTypesAndValues.new(spec, config) : ForTypes.new(Array(spec))
26
26
  end
27
27
 
28
- def self.new(success:, failure:)
29
- Evaluator.new(ToEnsure[success], ToEnsure[failure])
30
- end
31
-
32
- @nil_as_valid_value_checking = false
33
-
34
- def self.nil_as_valid_value_checking!(enabled: true)
35
- @nil_as_valid_value_checking = enabled
36
- end
37
-
38
- def self.nil_as_valid_value_checking?
39
- @nil_as_valid_value_checking
28
+ def self.new(success:, failure:, config:)
29
+ Evaluator.new(ToEnsure[success, config], ToEnsure[failure, config])
40
30
  end
41
31
 
42
32
  private_constant :ToEnsure
@@ -2,6 +2,15 @@
2
2
 
3
3
  class BCDD::Result
4
4
  module Expectations::Mixin
5
+ module Factory
6
+ def self.module!
7
+ ::Module.new do
8
+ def self.included(base); base.const_set(:ResultExpectationsMixin, self); end
9
+ def self.extended(base); base.const_set(:ResultExpectationsMixin, self); end
10
+ end
11
+ end
12
+ end
13
+
5
14
  METHODS = <<~RUBY
6
15
  def Success(...)
7
16
  _Result.Success(...)
@@ -25,17 +34,10 @@ class BCDD::Result
25
34
  end
26
35
  end
27
36
 
28
- OPTIONS = { Continue: Continuable }.freeze
29
-
30
- def self.options(names)
31
- Array(names).filter_map { |name| OPTIONS[name] }
32
- end
33
- end
37
+ OPTIONS = { continue: Continuable }.freeze
34
38
 
35
- def self.module!
36
- ::Module.new do
37
- def self.included(base); base.const_set(:ResultExpectationsMixin, self); end
38
- def self.extended(base); base.const_set(:ResultExpectationsMixin, self); end
39
+ def self.options(config_flags)
40
+ Config::Options.addon(map: config_flags, from: OPTIONS)
39
41
  end
40
42
  end
41
43
  end
@@ -4,22 +4,46 @@ class BCDD::Result
4
4
  class Expectations
5
5
  require_relative 'expectations/mixin'
6
6
 
7
- def self.mixin(success: nil, failure: nil, with: nil)
8
- addons = Mixin::Addons.options(with)
7
+ def self.mixin(**options)
8
+ return mixin!(**options) if Config.instance.feature.enabled?(:expectations)
9
9
 
10
- mod = Mixin.module!
11
- mod.const_set(:Result, new(success: success, failure: failure).freeze)
12
- mod.module_eval(Mixin::METHODS)
10
+ result_factory_without_expectations.mixin(**options.slice(:config))
11
+ end
12
+
13
+ def self.mixin!(success: nil, failure: nil, config: nil)
14
+ addons = mixin_module::Addons.options(config)
15
+
16
+ mod = mixin_module::Factory.module!
17
+ mod.const_set(:Result, new(success: success, failure: failure, config: config).freeze)
18
+ mod.module_eval(mixin_module::METHODS)
13
19
  mod.send(:include, *addons) unless addons.empty?
14
20
  mod
15
21
  end
16
22
 
17
- def initialize(subject: nil, success: nil, failure: nil, contract: nil)
23
+ def self.mixin_module
24
+ Mixin
25
+ end
26
+
27
+ def self.result_factory_without_expectations
28
+ ::BCDD::Result
29
+ end
30
+
31
+ def self.new(...)
32
+ return result_factory_without_expectations unless Config.instance.feature.enabled?(:expectations)
33
+
34
+ instance = allocate
35
+ instance.send(:initialize, ...)
36
+ instance
37
+ end
38
+
39
+ private_class_method :mixin!, :mixin_module, :result_factory_without_expectations
40
+
41
+ def initialize(subject: nil, success: nil, failure: nil, contract: nil, config: nil)
18
42
  @subject = subject
19
43
 
20
44
  @contract = contract if contract.is_a?(Contract::Evaluator)
21
45
 
22
- @contract ||= Contract.new(success: success, failure: failure).freeze
46
+ @contract ||= Contract.new(success: success, failure: failure, config: config).freeze
23
47
  end
24
48
 
25
49
  def Success(type, value = nil)
@@ -2,6 +2,15 @@
2
2
 
3
3
  class BCDD::Result
4
4
  module Mixin
5
+ module Factory
6
+ def self.module!
7
+ ::Module.new do
8
+ def self.included(base); base.const_set(:ResultMixin, self); end
9
+ def self.extended(base); base.const_set(:ResultMixin, self); end
10
+ end
11
+ end
12
+ end
13
+
5
14
  module Methods
6
15
  def Success(type, value = nil)
7
16
  Success.new(type: type, value: value, subject: self)
@@ -19,27 +28,31 @@ class BCDD::Result
19
28
  end
20
29
  end
21
30
 
22
- OPTIONS = { Continue: Continuable }.freeze
23
-
24
- def self.options(names)
25
- Array(names).filter_map { |name| OPTIONS[name] }
26
- end
27
- end
31
+ OPTIONS = { continue: Continuable }.freeze
28
32
 
29
- def self.module!
30
- ::Module.new do
31
- def self.included(base); base.const_set(:ResultMixin, self); end
32
- def self.extended(base); base.const_set(:ResultMixin, self); end
33
+ def self.options(config_flags)
34
+ Config::Options.addon(map: config_flags, from: OPTIONS)
33
35
  end
34
36
  end
35
37
  end
36
38
 
37
- def self.mixin(with: nil)
38
- addons = Mixin::Addons.options(with)
39
+ def self.mixin(config: nil)
40
+ addons = mixin_module::Addons.options(config)
39
41
 
40
- mod = Mixin.module!
41
- mod.send(:include, Mixin::Methods)
42
+ mod = mixin_module::Factory.module!
43
+ mod.send(:include, mixin_module::Methods)
44
+ mod.const_set(:Result, result_factory)
42
45
  mod.send(:include, *addons) unless addons.empty?
43
46
  mod
44
47
  end
48
+
49
+ def self.mixin_module
50
+ Mixin
51
+ end
52
+
53
+ def self.result_factory
54
+ ::BCDD::Result
55
+ end
56
+
57
+ private_class_method :mixin_module, :result_factory
45
58
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BCDD
4
4
  class Result
5
- VERSION = '0.7.0'
5
+ VERSION = '0.8.0'
6
6
  end
7
7
  end
data/lib/bcdd/result.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative 'result/version'
4
4
  require_relative 'result/error'
5
5
  require_relative 'result/data'
6
+ require_relative 'result/config'
6
7
  require_relative 'result/handler'
7
8
  require_relative 'result/failure'
8
9
  require_relative 'result/success'
@@ -20,6 +21,16 @@ class BCDD::Result
20
21
 
21
22
  private :unknown, :unknown=, :type_checker
22
23
 
24
+ def self.config
25
+ Config.instance
26
+ end
27
+
28
+ def self.configuration
29
+ yield(config)
30
+
31
+ config.freeze
32
+ end
33
+
23
34
  def initialize(type:, value:, subject: nil, expectations: nil)
24
35
  data = Data.new(name, type, value)
25
36
 
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bcdd/result'