dry-logic 1.0.2 → 1.0.8

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -26
  3. data/LICENSE +1 -1
  4. data/README.md +12 -14
  5. data/dry-logic.gemspec +27 -14
  6. data/lib/dry-logic.rb +1 -1
  7. data/lib/dry/logic.rb +2 -2
  8. data/lib/dry/logic/evaluator.rb +1 -1
  9. data/lib/dry/logic/operations.rb +11 -11
  10. data/lib/dry/logic/operations/abstract.rb +6 -6
  11. data/lib/dry/logic/operations/and.rb +4 -4
  12. data/lib/dry/logic/operations/attr.rb +1 -1
  13. data/lib/dry/logic/operations/binary.rb +1 -1
  14. data/lib/dry/logic/operations/check.rb +4 -4
  15. data/lib/dry/logic/operations/each.rb +2 -2
  16. data/lib/dry/logic/operations/implication.rb +2 -2
  17. data/lib/dry/logic/operations/key.rb +5 -5
  18. data/lib/dry/logic/operations/negation.rb +2 -2
  19. data/lib/dry/logic/operations/or.rb +2 -2
  20. data/lib/dry/logic/operations/set.rb +3 -3
  21. data/lib/dry/logic/operations/unary.rb +1 -1
  22. data/lib/dry/logic/operations/xor.rb +2 -2
  23. data/lib/dry/logic/operators.rb +4 -4
  24. data/lib/dry/logic/predicates.rb +60 -28
  25. data/lib/dry/logic/result.rb +2 -2
  26. data/lib/dry/logic/rule.rb +11 -11
  27. data/lib/dry/logic/rule/interface.rb +32 -37
  28. data/lib/dry/logic/rule/predicate.rb +3 -3
  29. data/lib/dry/logic/rule_compiler.rb +3 -3
  30. data/lib/dry/logic/version.rb +1 -1
  31. metadata +12 -133
  32. data/.codeclimate.yml +0 -15
  33. data/.gitignore +0 -9
  34. data/.rspec +0 -3
  35. data/.travis.yml +0 -31
  36. data/CONTRIBUTING.md +0 -29
  37. data/Gemfile +0 -15
  38. data/Rakefile +0 -14
  39. data/benchmarks/rule_application.rb +0 -30
  40. data/benchmarks/setup.rb +0 -13
  41. data/bin/console +0 -11
  42. data/examples/basic.rb +0 -16
  43. data/spec/integration/result_spec.rb +0 -61
  44. data/spec/integration/rule_spec.rb +0 -55
  45. data/spec/shared/predicates.rb +0 -59
  46. data/spec/shared/rule.rb +0 -74
  47. data/spec/spec_helper.rb +0 -36
  48. data/spec/support/mutant.rb +0 -11
  49. data/spec/unit/operations/and_spec.rb +0 -70
  50. data/spec/unit/operations/attr_spec.rb +0 -29
  51. data/spec/unit/operations/check_spec.rb +0 -51
  52. data/spec/unit/operations/each_spec.rb +0 -49
  53. data/spec/unit/operations/implication_spec.rb +0 -32
  54. data/spec/unit/operations/key_spec.rb +0 -135
  55. data/spec/unit/operations/negation_spec.rb +0 -51
  56. data/spec/unit/operations/or_spec.rb +0 -75
  57. data/spec/unit/operations/set_spec.rb +0 -43
  58. data/spec/unit/operations/xor_spec.rb +0 -63
  59. data/spec/unit/predicates/array_spec.rb +0 -43
  60. data/spec/unit/predicates/attr_spec.rb +0 -31
  61. data/spec/unit/predicates/bool_spec.rb +0 -36
  62. data/spec/unit/predicates/case_spec.rb +0 -35
  63. data/spec/unit/predicates/date_spec.rb +0 -33
  64. data/spec/unit/predicates/date_time_spec.rb +0 -33
  65. data/spec/unit/predicates/decimal_spec.rb +0 -34
  66. data/spec/unit/predicates/empty_spec.rb +0 -40
  67. data/spec/unit/predicates/eql_spec.rb +0 -23
  68. data/spec/unit/predicates/even_spec.rb +0 -33
  69. data/spec/unit/predicates/excluded_from_spec.rb +0 -37
  70. data/spec/unit/predicates/excludes_spec.rb +0 -58
  71. data/spec/unit/predicates/false_spec.rb +0 -37
  72. data/spec/unit/predicates/filled_spec.rb +0 -40
  73. data/spec/unit/predicates/float_spec.rb +0 -33
  74. data/spec/unit/predicates/format_spec.rb +0 -23
  75. data/spec/unit/predicates/gt_spec.rb +0 -42
  76. data/spec/unit/predicates/gteq_spec.rb +0 -42
  77. data/spec/unit/predicates/included_in_spec.rb +0 -37
  78. data/spec/unit/predicates/includes_spec.rb +0 -24
  79. data/spec/unit/predicates/int_spec.rb +0 -36
  80. data/spec/unit/predicates/key_spec.rb +0 -31
  81. data/spec/unit/predicates/lt_spec.rb +0 -42
  82. data/spec/unit/predicates/lteq_spec.rb +0 -42
  83. data/spec/unit/predicates/max_size_spec.rb +0 -51
  84. data/spec/unit/predicates/min_size_spec.rb +0 -51
  85. data/spec/unit/predicates/none_spec.rb +0 -30
  86. data/spec/unit/predicates/not_eql_spec.rb +0 -23
  87. data/spec/unit/predicates/number_spec.rb +0 -39
  88. data/spec/unit/predicates/odd_spec.rb +0 -33
  89. data/spec/unit/predicates/respond_to_spec.rb +0 -31
  90. data/spec/unit/predicates/size_spec.rb +0 -57
  91. data/spec/unit/predicates/str_spec.rb +0 -34
  92. data/spec/unit/predicates/time_spec.rb +0 -33
  93. data/spec/unit/predicates/true_spec.rb +0 -37
  94. data/spec/unit/predicates/type_spec.rb +0 -37
  95. data/spec/unit/predicates/uuid_v4_spec.rb +0 -29
  96. data/spec/unit/predicates_spec.rb +0 -25
  97. data/spec/unit/rule/predicate_spec.rb +0 -55
  98. data/spec/unit/rule_compiler_spec.rb +0 -129
  99. data/spec/unit/rule_spec.rb +0 -213
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry-logic'
4
-
5
- RSpec.describe 'Rules' do
6
- specify 'defining an anonymous rule with an arbitrary predicate' do
7
- rule = Dry::Logic.Rule { |value| value.is_a?(Integer) }
8
-
9
- expect(rule.(1)).to be_success
10
- expect(rule[1]).to be(true)
11
- end
12
-
13
- specify 'defining a conjunction' do
14
- rule = Dry::Logic.Rule(&:even?) & Dry::Logic.Rule { |v| v > 4 }
15
-
16
- expect(rule.(3)).to be_failure
17
- expect(rule.(4)).to be_failure
18
- expect(rule.(5)).to be_failure
19
- expect(rule.(6)).to be_success
20
- end
21
-
22
- specify 'defining a disjunction' do
23
- rule = Dry::Logic.Rule { |v| v < 4 } | Dry::Logic.Rule { |v| v > 6 }
24
-
25
- expect(rule.(5)).to be_failure
26
- expect(rule.(3)).to be_success
27
- expect(rule.(7)).to be_success
28
- end
29
-
30
- specify 'defining an implication' do
31
- rule = Dry::Logic.Rule(&:empty?) > Dry::Logic.Rule { |v| v.is_a?(Array) }
32
-
33
- expect(rule.('foo')).to be_success
34
- expect(rule.([1, 2])).to be_success
35
- expect(rule.([])).to be_success
36
- expect(rule.('')).to be_failure
37
- end
38
-
39
- specify 'defining an exclusive disjunction' do
40
- rule = Dry::Logic.Rule(&:empty?) ^ Dry::Logic.Rule { |v| v.is_a?(Array) }
41
-
42
- expect(rule.('foo')).to be_failure
43
- expect(rule.([])).to be_failure
44
- expect(rule.([1, 2])).to be_success
45
- expect(rule.('')).to be_success
46
- end
47
-
48
- specify 'defining a rule with options' do
49
- rule = Dry::Logic::Rule(id: :empty?) { |value| value.empty? }
50
-
51
- expect(rule.('foo')).to be_failure
52
- expect(rule.('')).to be_success
53
- expect(rule.ast('foo')).to eql([:predicate, [:empty?, [[:value, 'foo']]]])
54
- end
55
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/logic/predicates'
4
-
5
- RSpec.shared_examples 'predicates' do
6
- let(:nil?) { Dry::Logic::Predicates[:nil?] }
7
-
8
- let(:array?) { Dry::Logic::Predicates[:array?] }
9
-
10
- let(:empty?) { Dry::Logic::Predicates[:empty?] }
11
-
12
- let(:str?) { Dry::Logic::Predicates[:str?] }
13
-
14
- let(:true?) { Dry::Logic::Predicates[:true?] }
15
-
16
- let(:hash?) { Dry::Logic::Predicates[:hash?] }
17
-
18
- let(:int?) { Dry::Logic::Predicates[:int?] }
19
-
20
- let(:filled?) { Dry::Logic::Predicates[:filled?] }
21
-
22
- let(:min_size?) { Dry::Logic::Predicates[:min_size?] }
23
-
24
- let(:lt?) { Dry::Logic::Predicates[:lt?] }
25
-
26
- let(:gt?) { Dry::Logic::Predicates[:gt?] }
27
-
28
- let(:key?) { Dry::Logic::Predicates[:key?] }
29
-
30
- let(:attr?) { Dry::Logic::Predicates[:attr?] }
31
-
32
- let(:eql?) { Dry::Logic::Predicates[:eql?] }
33
-
34
- let(:size?) { Dry::Logic::Predicates[:size?] }
35
-
36
- let(:case?) { Dry::Logic::Predicates[:case?] }
37
-
38
- let(:equal?) { Dry::Logic::Predicates[:equal?] }
39
- end
40
-
41
- RSpec.shared_examples 'a passing predicate' do
42
- let(:predicate) { Dry::Logic::Predicates[predicate_name] }
43
-
44
- it do
45
- arguments_list.each do |args|
46
- expect(predicate.call(*args)).to be(true)
47
- end
48
- end
49
- end
50
-
51
- RSpec.shared_examples 'a failing predicate' do
52
- let(:predicate) { Dry::Logic::Predicates[predicate_name] }
53
-
54
- it do
55
- arguments_list.each do |args|
56
- expect(predicate.call(*args)).to be(false)
57
- end
58
- end
59
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for Dry::Logic::Rule do
4
- let(:arity) { 2 }
5
- let(:predicate) { double(:predicate, arity: arity, name: predicate_name) }
6
- let(:rule_type) { described_class }
7
- let(:predicate_name) { :good? }
8
-
9
- describe '#arity' do
10
- it 'returns its predicate arity' do
11
- rule = rule_type.build(predicate)
12
-
13
- expect(rule.arity).to be(2)
14
- end
15
- end
16
-
17
- describe '#parameters' do
18
- it 'returns a list of args with their names' do
19
- rule = rule_type.build(-> foo, bar { true }, args: [312])
20
-
21
- expect(rule.parameters).to eql([[:req, :foo], [:req, :bar]])
22
- end
23
- end
24
-
25
- describe '#call' do
26
- let(:arity) { 1 }
27
-
28
- it 'returns success for valid input' do
29
- rule = rule_type.build(predicate)
30
-
31
- expect(predicate).to receive(:[]).with(2).and_return(true)
32
-
33
- expect(rule.(2)).to be_success
34
- end
35
-
36
- it 'returns failure for invalid input' do
37
- rule = rule_type.build(predicate)
38
-
39
- expect(predicate).to receive(:[]).with(2).and_return(false)
40
-
41
- expect(rule.(2)).to be_failure
42
- end
43
- end
44
-
45
- describe '#[]' do
46
- let(:arity) { 1 }
47
-
48
- it 'delegates to its predicate' do
49
- rule = rule_type.build(predicate)
50
-
51
- expect(predicate).to receive(:[]).with(2).and_return(true)
52
- expect(rule[2]).to be(true)
53
- end
54
- end
55
-
56
- describe '#curry' do
57
- it 'returns a curried rule' do
58
- rule = rule_type.build(predicate).curry(3)
59
-
60
- expect(predicate).to receive(:[]).with(3, 2).and_return(true)
61
- expect(rule.args).to eql([3])
62
-
63
- expect(rule.(2)).to be_success
64
- end
65
-
66
- it 'raises argument error when arity does not match' do
67
- expect(predicate).to receive(:arity).and_return(2)
68
-
69
- expect { rule_type.build(predicate).curry(3, 2, 1) }.to raise_error(
70
- ArgumentError, 'wrong number of arguments (3 for 2)'
71
- )
72
- end
73
- end
74
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
4
- require 'yaml'
5
- rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
6
- latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
7
-
8
- if RUBY_VERSION == latest_mri
9
- require 'simplecov'
10
- SimpleCov.start do
11
- add_filter '/spec/'
12
- end
13
- end
14
- end
15
-
16
- begin
17
- require 'pry-byebug'
18
- rescue LoadError; end
19
-
20
- require 'dry-logic'
21
- require 'dry/core/constants'
22
- require 'pathname'
23
-
24
- SPEC_ROOT = Pathname(__dir__)
25
-
26
- Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require))
27
- Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require))
28
-
29
- include Dry::Logic
30
- include Dry::Core::Constants
31
-
32
- RSpec.configure do |config|
33
- config.disable_monkey_patching!
34
-
35
- config.warnings = true
36
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mutant
4
- class Selector
5
- class Expression < self
6
- def call(_subject)
7
- integration.all_tests
8
- end
9
- end
10
- end
11
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Operations::And do
4
- subject(:operation) { Operations::And.new(left, right) }
5
-
6
- include_context 'predicates'
7
-
8
- let(:left) { Rule::Predicate.build(int?) }
9
- let(:right) { Rule::Predicate.build(gt?).curry(18) }
10
-
11
- describe '#call' do
12
- it 'calls left and right' do
13
- expect(operation.(18)).to be_failure
14
- end
15
- end
16
-
17
- describe '#to_ast' do
18
- it 'returns ast' do
19
- expect(operation.to_ast).to eql(
20
- [:and, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]]
21
- )
22
- end
23
-
24
- it 'returns result ast' do
25
- expect(operation.('18').to_ast).to eql(
26
- [:and, [[:predicate, [:int?, [[:input, '18']]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, '18']]]]]]]
27
- )
28
-
29
- expect(operation.with(hints: false).('18').to_ast).to eql(
30
- [:predicate, [:int?, [[:input, '18']]]]
31
- )
32
-
33
- expect(operation.(18).to_ast).to eql(
34
- [:predicate, [:gt?, [[:num, 18], [:input, 18]]]]
35
- )
36
- end
37
-
38
- it 'returns failure result ast' do
39
- expect(operation.with(id: :age).('18').to_ast).to eql(
40
- [:failure, [:age, [:and, [[:predicate, [:int?, [[:input, '18']]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, '18']]]]]]]]]
41
- )
42
-
43
- expect(operation.with(id: :age).(18).to_ast).to eql(
44
- [:failure, [:age, [:predicate, [:gt?, [[:num, 18], [:input, 18]]]]]]
45
- )
46
- end
47
- end
48
-
49
- describe '#and' do
50
- let(:other) { Rule::Predicate.build(lt?).curry(30) }
51
-
52
- it 'creates and with the other' do
53
- expect(operation.and(other).(31)).to be_failure
54
- end
55
- end
56
-
57
- describe '#or' do
58
- let(:other) { Rule::Predicate.build(lt?).curry(14) }
59
-
60
- it 'creates or with the other' do
61
- expect(operation.or(other).(13)).to be_success
62
- end
63
- end
64
-
65
- describe '#to_s' do
66
- it 'returns string representation' do
67
- expect(operation.to_s).to eql('int? AND gt?(18)')
68
- end
69
- end
70
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Operations::Attr do
4
- subject(:operation) { Operations::Attr.new(Rule::Predicate.build(str?), name: :name) }
5
-
6
- include_context 'predicates'
7
-
8
- let(:model) { Struct.new(:name) }
9
-
10
- describe '#call' do
11
- it 'applies predicate to the value' do
12
- expect(operation.(model.new('Jane'))).to be_success
13
- expect(operation.(model.new(nil))).to be_failure
14
- end
15
- end
16
-
17
- describe '#and' do
18
- let(:other) { Operations::Attr.new(Rule::Predicate.build(min_size?).curry(3), name: :name) }
19
-
20
- it 'returns and where value is passed to the right' do
21
- present_and_string = operation.and(other)
22
-
23
- expect(present_and_string.(model.new('Jane'))).to be_success
24
-
25
- expect(present_and_string.(model.new('Ja'))).to be_failure
26
- expect(present_and_string.(model.new(1))).to be_failure
27
- end
28
- end
29
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Operations::Check do
4
- include_context 'predicates'
5
-
6
- describe '#call' do
7
- context 'with 1-level nesting' do
8
- subject(:operation) do
9
- Operations::Check.new(Rule::Predicate.build(eql?).curry(1), id: :compare, keys: [:num])
10
- end
11
-
12
- it 'applies predicate to args extracted from the input' do
13
- expect(operation.(num: 1)).to be_success
14
- expect(operation.(num: 2)).to be_failure
15
- end
16
- end
17
-
18
- context 'with 2-levels nesting' do
19
- subject(:operation) do
20
- Operations::Check.new(Rule::Predicate.build(eql?), id: :compare, keys: [[:nums, :left], [:nums, :right]])
21
- end
22
-
23
- it 'applies predicate to args extracted from the input' do
24
- expect(operation.(nums: { left: 1, right: 1 })).to be_success
25
- expect(operation.(nums: { left: 1, right: 2 })).to be_failure
26
- end
27
-
28
- it 'curries args properly' do
29
- result = operation.(nums: { left: 1, right: 2 })
30
-
31
- expect(result.to_ast).to eql(
32
- [:failure, [:compare, [:check, [
33
- [[:nums, :left], [:nums, :right]], [:predicate, [:eql?, [[:left, 1], [:right, 2]]]]]
34
- ]]]
35
- )
36
- end
37
- end
38
- end
39
-
40
- describe '#to_ast' do
41
- subject(:operation) do
42
- Operations::Check.new(Rule::Predicate.build(str?), keys: [:email])
43
- end
44
-
45
- it 'returns ast' do
46
- expect(operation.to_ast).to eql(
47
- [:check, [[:email], [:predicate, [:str?, [[:input, Undefined]]]]]]
48
- )
49
- end
50
- end
51
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Operations::Each do
4
- subject(:operation) { Operations::Each.new(is_string) }
5
-
6
- include_context 'predicates'
7
-
8
- let(:is_string) { Rule::Predicate.build(str?) }
9
-
10
- describe '#call' do
11
- it 'applies its rules to all elements in the input' do
12
- expect(operation.(['Address'])).to be_success
13
-
14
- expect(operation.([nil, 'Address'])).to be_failure
15
- expect(operation.([:Address, 'Address'])).to be_failure
16
- end
17
- end
18
-
19
- describe '#to_ast' do
20
- it 'returns ast' do
21
- expect(operation.to_ast).to eql([:each, [:predicate, [:str?, [[:input, Undefined]]]]])
22
- end
23
-
24
- it 'returns result ast' do
25
- expect(operation.([nil, 12, nil]).to_ast).to eql(
26
- [:set, [
27
- [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]],
28
- [:key, [1, [:predicate, [:str?, [[:input, 12]]]]]],
29
- [:key, [2, [:predicate, [:str?, [[:input, nil]]]]]]
30
- ]]
31
- )
32
- end
33
-
34
- it 'returns failure result ast' do
35
- expect(operation.with(id: :tags).([nil, 'red', 12]).to_ast).to eql(
36
- [:failure, [:tags, [:set, [
37
- [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]],
38
- [:key, [2, [:predicate, [:str?, [[:input, 12]]]]]]
39
- ]]]]
40
- )
41
- end
42
- end
43
-
44
- describe '#to_s' do
45
- it 'returns string representation' do
46
- expect(operation.to_s).to eql('each(str?)')
47
- end
48
- end
49
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Operations::Implication do
4
- subject(:operation) { Operations::Implication.new(left, right) }
5
-
6
- include_context 'predicates'
7
-
8
- let(:left) { Rule::Predicate.build(int?) }
9
- let(:right) { Rule::Predicate.build(gt?).curry(18) }
10
-
11
- describe '#call' do
12
- it 'calls left and right' do
13
- expect(operation.('19')).to be_success
14
- expect(operation.(19)).to be_success
15
- expect(operation.(18)).to be_failure
16
- end
17
- end
18
-
19
- describe '#to_ast' do
20
- it 'returns ast' do
21
- expect(operation.to_ast).to eql(
22
- [:implication, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]]
23
- )
24
- end
25
- end
26
-
27
- describe '#to_s' do
28
- it 'returns string representation' do
29
- expect(operation.to_s).to eql('int? THEN gt?(18)')
30
- end
31
- end
32
- end