bcdd-result 0.7.0 → 0.8.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.
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'