dry-logic 0.1.4 → 0.2.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/README.md +6 -2
  4. data/lib/dry/logic/evaluator.rb +46 -0
  5. data/lib/dry/logic/predicate.rb +3 -3
  6. data/lib/dry/logic/result.rb +26 -126
  7. data/lib/dry/logic/result/each.rb +10 -0
  8. data/lib/dry/logic/result/multi.rb +14 -0
  9. data/lib/dry/logic/result/named.rb +17 -0
  10. data/lib/dry/logic/result/set.rb +10 -0
  11. data/lib/dry/logic/result/value.rb +13 -0
  12. data/lib/dry/logic/rule.rb +14 -36
  13. data/lib/dry/logic/rule/attr.rb +3 -11
  14. data/lib/dry/logic/rule/check.rb +23 -22
  15. data/lib/dry/logic/rule/composite.rb +32 -12
  16. data/lib/dry/logic/rule/each.rb +3 -3
  17. data/lib/dry/logic/rule/key.rb +24 -5
  18. data/lib/dry/logic/rule/negation.rb +15 -0
  19. data/lib/dry/logic/rule/set.rb +9 -8
  20. data/lib/dry/logic/rule/value.rb +15 -3
  21. data/lib/dry/logic/rule_compiler.rb +8 -40
  22. data/lib/dry/logic/version.rb +1 -1
  23. data/spec/shared/predicates.rb +2 -0
  24. data/spec/spec_helper.rb +1 -0
  25. data/spec/unit/rule/attr_spec.rb +5 -5
  26. data/spec/unit/rule/check_spec.rb +26 -39
  27. data/spec/unit/rule/conjunction_spec.rb +4 -4
  28. data/spec/unit/rule/disjunction_spec.rb +3 -3
  29. data/spec/unit/rule/each_spec.rb +2 -2
  30. data/spec/unit/rule/exclusive_disjunction_spec.rb +19 -0
  31. data/spec/unit/rule/implication_spec.rb +2 -2
  32. data/spec/unit/rule/key_spec.rb +103 -9
  33. data/spec/unit/rule/set_spec.rb +7 -9
  34. data/spec/unit/rule/value_spec.rb +29 -3
  35. data/spec/unit/rule_compiler_spec.rb +21 -49
  36. metadata +12 -9
  37. data/lib/dry/logic/rule/group.rb +0 -21
  38. data/lib/dry/logic/rule/result.rb +0 -33
  39. data/rakelib/rubocop.rake +0 -18
  40. data/spec/unit/rule/group_spec.rb +0 -12
  41. data/spec/unit/rule/result_spec.rb +0 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d99c501f48f756fd19ec5f5c3da47041b704e97a
4
- data.tar.gz: a8dd4c89cd7674a65b69a6d441091ba73b514bbc
3
+ metadata.gz: 556cffe1d07e756e85274f2af98b8d97245e4420
4
+ data.tar.gz: 86695fbdf83593baedb7dd73483c30f41f671280
5
5
  SHA512:
6
- metadata.gz: 68a19a8f78af8a329e47e8b089fef34fb2b9dbdd686840568b328fe755b65f9fbd7e7d9b6324aa78d68d7bba56c61f5c1a97fa51a9f7d89d39e3cc91e7c64503
7
- data.tar.gz: e5b68c7f5599a244f9918c5d2bc67b282983dc1e48cbd2deaaeabf55670dbbe5b05a27b5f5d9ce6655cf0de6f202eab50a08cb323e37e535aa0d80951ac60a31
6
+ metadata.gz: 05b63d57cfacd055926b256752698078dbca63afcf8bb03aff563cbecff5247e9dc66e999bad29cecc1fc5f37a15a5c6c76525cb1e6654f1c4915777825d5fa3
7
+ data.tar.gz: f1a65826b7049c3c5b0083b4c72feda8e911915898ff03a4d9acc5b4d6908954d9105edd36f611e535e0e0320b1f906f9ae658659a062b48ee18b564e1ab0abb
data/Gemfile CHANGED
@@ -3,7 +3,6 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :tools do
6
- gem 'rubocop'
7
6
  gem 'guard'
8
7
  gem 'guard-rspec'
9
8
  gem 'guard-rubocop'
data/README.md CHANGED
@@ -28,8 +28,12 @@ require 'dry/logic/predicates'
28
28
 
29
29
  include Dry::Logic
30
30
 
31
- user_present = Rule::Key.new(:user, Predicates[:key?])
32
- has_min_age = Rule::Key.new(:age, Predicates[:key?]) & Rule::Value.new(:age, Predicates[:gt?].curry(18))
31
+ user_present = Rule::Key.new(Predicates[:key?], name: :user)
32
+
33
+ has_min_age = Rule::Key.new(
34
+ Predicates[:key?]) & Rule::Value.new(:age, Predicates[:gt?].curry(18),
35
+ name: :age
36
+ )
33
37
 
34
38
  user_rule = user_present & has_min_age
35
39
 
@@ -0,0 +1,46 @@
1
+ module Dry
2
+ module Logic
3
+ class Evaluator
4
+ include Dry::Equalizer(:path)
5
+
6
+ attr_reader :path
7
+
8
+ class Set
9
+ include Dry::Equalizer(:evaluators)
10
+
11
+ attr_reader :evaluators
12
+
13
+ def self.new(paths)
14
+ super(paths.map { |path| Evaluator::Key.new(path) })
15
+ end
16
+
17
+ def initialize(evaluators)
18
+ @evaluators = evaluators
19
+ end
20
+
21
+ def call(input)
22
+ evaluators.map { |evaluator| evaluator[input] }
23
+ end
24
+ alias_method :[], :call
25
+ end
26
+
27
+ class Key < Evaluator
28
+ def call(input)
29
+ path.reduce(input) { |a, e| a[e] }
30
+ end
31
+ alias_method :[], :call
32
+ end
33
+
34
+ class Attr < Evaluator
35
+ def call(input)
36
+ path.reduce(input) { |a, e| a.public_send(e) }
37
+ end
38
+ alias_method :[], :call
39
+ end
40
+
41
+ def initialize(path)
42
+ @path = Array(path)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -8,7 +8,7 @@ module Dry
8
8
  end
9
9
 
10
10
  class Predicate
11
- include Dry::Equalizer(:id)
11
+ include Dry::Equalizer(:id, :args)
12
12
 
13
13
  attr_reader :id, :args, :fn
14
14
 
@@ -26,10 +26,10 @@ module Dry
26
26
  self.class.new(id, *args, &fn.curry.(*args))
27
27
  end
28
28
 
29
- def to_ary
29
+ def to_ast
30
30
  [:predicate, [id, args]]
31
31
  end
32
- alias_method :to_a, :to_ary
32
+ alias_method :to_a, :to_ast
33
33
  end
34
34
  end
35
35
  end
@@ -1,155 +1,55 @@
1
1
  module Dry
2
2
  module Logic
3
- def self.Result(input, value, rule)
4
- case value
5
- when Result
6
- value.class.new(value.input, value.success?, rule)
7
- when Array
8
- Result::Set.new(input, value, rule)
9
- else
10
- Result::Value.new(input, value, rule)
11
- end
3
+ def self.Result(response, rule, input)
4
+ Result[rule].new(response, rule, input)
12
5
  end
13
6
 
14
7
  class Result
15
8
  include Dry::Equalizer(:success?, :input, :rule)
16
9
 
17
- attr_reader :input, :value, :rule, :name
18
-
19
- class Result::Set < Result
20
- def success?
21
- value.all?(&:success?)
22
- end
23
-
24
- def to_ary
25
- indices = value.map { |v| v.failure? ? value.index(v) : nil }.compact
26
- [:input, [name, input, value.values_at(*indices).map(&:to_ary)]]
27
- end
28
- end
29
-
30
- class Result::Value < Result
31
- def to_ary
32
- [:input, [name, input, [rule.to_ary]]]
33
- end
34
- alias_method :to_a, :to_ary
35
- end
36
-
37
- class Result::LazyValue < Result
38
- def to_ary
39
- [:input, [rule.name, input, [rule.to_ary]]]
40
- end
41
- alias_method :to_a, :to_ary
10
+ attr_reader :input, :rule, :response, :success
42
11
 
43
- def input
44
- success? ? rule.evaluate_input(@input) : @input
12
+ def self.[](type)
13
+ case type
14
+ when Rule::Each then Result::Each
15
+ when Rule::Set then Result::Set
16
+ when Rule::Key, Rule::Attr, Rule::Check then Result::Named
17
+ else Result::Value
45
18
  end
46
19
  end
47
20
 
48
- class Result::Wrapped < Result::Value
49
- def to_ary
50
- [:input, [rule.name, rule.evaluate_input(input), [rule.to_ary]]]
51
- end
52
- alias_method :to_a, :to_ary
53
-
54
- def wrapped?
55
- true
56
- end
57
- end
58
-
59
- class Result::Verified < Result
60
- attr_reader :predicate_id
61
-
62
- def initialize(result, predicate_id)
63
- @input = result.input
64
- @value = result.value
65
- @rule = result.rule
66
- @name = result.name
67
- @predicate_id = predicate_id
68
- end
69
-
70
- def call(*)
71
- Logic.Result(input, success?, rule)
72
- end
73
-
74
- def to_ary
75
- [:input, [name, input, [rule.to_ary]]]
76
- end
77
- alias_method :to_a, :to_ary
78
-
79
- def success?
80
- rule.predicate_id == predicate_id
81
- end
82
- end
83
-
84
- def initialize(input, value, rule)
85
- @input = input
86
- @value = value
21
+ def initialize(response, rule, input)
22
+ @response = response
23
+ @success = response.respond_to?(:success?) ? response.success? : response
87
24
  @rule = rule
88
- @name = rule.name
25
+ @input = input
89
26
  end
90
27
 
91
- def call(*)
92
- self
28
+ def [](name)
29
+ response[name] if response.respond_to?(:[])
93
30
  end
94
31
 
95
- def curry(predicate_id = nil)
96
- if predicate_id
97
- Result::Verified.new(self, predicate_id)
98
- else
99
- self
100
- end
32
+ def name
33
+ nil
101
34
  end
102
35
 
103
36
  def negated
104
- self.class.new(input, !value, rule)
105
- end
106
-
107
- def then(other)
108
- if success?
109
- other.(input)
110
- else
111
- Logic.Result(input, true, rule)
112
- end
113
- end
114
-
115
- def and(other)
116
- if success?
117
- other.(input)
118
- else
119
- self
120
- end
121
- end
122
-
123
- def or(other)
124
- if success?
125
- self
126
- else
127
- other.(input)
128
- end
129
- end
130
-
131
- def xor(other)
132
- other_result = other.(input)
133
- value = success? ^ other_result.success?
134
-
135
- if other_result.wrapped?
136
- Result::Wrapped.new(input, value, rule)
137
- else
138
- Logic.Result(other_result.input, value, rule)
139
- end
37
+ self.class.new(!success, rule, input)
140
38
  end
141
39
 
142
40
  def success?
143
- @value
41
+ @success
144
42
  end
145
43
 
146
44
  def failure?
147
- ! success?
148
- end
149
-
150
- def wrapped?
151
- false
45
+ !success?
152
46
  end
153
47
  end
154
48
  end
155
49
  end
50
+
51
+ require 'dry/logic/result/value'
52
+ require 'dry/logic/result/named'
53
+ require 'dry/logic/result/multi'
54
+ require 'dry/logic/result/each'
55
+ require 'dry/logic/result/set'
@@ -0,0 +1,10 @@
1
+ module Dry
2
+ module Logic
3
+ class Result::Each < Result::Multi
4
+ def to_ast
5
+ failed_rules = failures.map { |el| [:el, [success.index(el), el.to_ast]] }
6
+ [:result, [rule.evaluate(input), [:each, failed_rules]]]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Dry
2
+ module Logic
3
+ class Result::Multi < Result
4
+ def success?
5
+ success.all?(&:success?)
6
+ end
7
+
8
+ def failures
9
+ indices = success.map { |v| v.failure? ? success.index(v) : nil }.compact
10
+ success.values_at(*indices)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Dry
2
+ module Logic
3
+ class Result::Named < Result::Value
4
+ def name
5
+ rule.name
6
+ end
7
+
8
+ def to_ast
9
+ if response.respond_to?(:to_ast) && !response.is_a?(Result)
10
+ response.to_ast
11
+ else
12
+ [:input, [rule.name, super]]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Dry
2
+ module Logic
3
+ class Result::Set < Result::Multi
4
+ def to_ast
5
+ failed_rules = failures.map { |el| el.to_ast }
6
+ [:result, [rule.evaluate(input), [:set, failed_rules]]]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Dry
2
+ module Logic
3
+ class Result::Value < Result
4
+ def to_ast
5
+ if response.respond_to?(:to_ast)
6
+ response.to_ast
7
+ else
8
+ [:result, [rule.evaluate(input), rule.to_ast]]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,31 +1,15 @@
1
1
  module Dry
2
2
  module Logic
3
3
  class Rule
4
- include Dry::Equalizer(:name, :predicate)
4
+ include Dry::Equalizer(:predicate, :options)
5
5
 
6
- attr_reader :name, :predicate
6
+ attr_reader :predicate
7
7
 
8
- class Negation < Rule
9
- include Dry::Equalizer(:rule)
8
+ attr_reader :options
10
9
 
11
- attr_reader :rule
12
-
13
- def initialize(rule)
14
- @rule = rule
15
- end
16
-
17
- def call(*args)
18
- rule.(*args).negated
19
- end
20
-
21
- def to_ary
22
- [:not, rule.to_ary]
23
- end
24
- end
25
-
26
- def initialize(name, predicate)
27
- @name = name
10
+ def initialize(predicate, options = {})
28
11
  @predicate = predicate
12
+ @options = options
29
13
  end
30
14
 
31
15
  def predicate_id
@@ -33,18 +17,9 @@ module Dry
33
17
  end
34
18
 
35
19
  def type
36
- :rule
37
- end
38
-
39
- def call(*args)
40
- Logic.Result(args, predicate.call, self)
20
+ raise NotImplementedError
41
21
  end
42
22
 
43
- def to_ary
44
- [type, [name, predicate.to_ary]]
45
- end
46
- alias_method :to_a, :to_ary
47
-
48
23
  def and(other)
49
24
  Conjunction.new(self, other)
50
25
  end
@@ -70,24 +45,27 @@ module Dry
70
45
  end
71
46
 
72
47
  def new(predicate)
73
- self.class.new(name, predicate)
48
+ self.class.new(predicate, options)
74
49
  end
75
50
 
76
51
  def curry(*args)
77
- self.class.new(name, predicate.curry(*args))
52
+ self.class.new(predicate.curry(*args), options)
53
+ end
54
+
55
+ def each?
56
+ predicate.is_a?(Rule::Each)
78
57
  end
79
58
  end
80
59
  end
81
60
  end
82
61
 
62
+ require 'dry/logic/rule/value'
83
63
  require 'dry/logic/rule/key'
84
64
  require 'dry/logic/rule/attr'
85
- require 'dry/logic/rule/value'
86
65
  require 'dry/logic/rule/each'
87
66
  require 'dry/logic/rule/set'
88
67
  require 'dry/logic/rule/composite'
68
+ require 'dry/logic/rule/negation'
89
69
  require 'dry/logic/rule/check'
90
- require 'dry/logic/rule/result'
91
- require 'dry/logic/rule/group'
92
70
 
93
71
  require 'dry/logic/result'