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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +6 -2
- data/lib/dry/logic/evaluator.rb +46 -0
- data/lib/dry/logic/predicate.rb +3 -3
- data/lib/dry/logic/result.rb +26 -126
- data/lib/dry/logic/result/each.rb +10 -0
- data/lib/dry/logic/result/multi.rb +14 -0
- data/lib/dry/logic/result/named.rb +17 -0
- data/lib/dry/logic/result/set.rb +10 -0
- data/lib/dry/logic/result/value.rb +13 -0
- data/lib/dry/logic/rule.rb +14 -36
- data/lib/dry/logic/rule/attr.rb +3 -11
- data/lib/dry/logic/rule/check.rb +23 -22
- data/lib/dry/logic/rule/composite.rb +32 -12
- data/lib/dry/logic/rule/each.rb +3 -3
- data/lib/dry/logic/rule/key.rb +24 -5
- data/lib/dry/logic/rule/negation.rb +15 -0
- data/lib/dry/logic/rule/set.rb +9 -8
- data/lib/dry/logic/rule/value.rb +15 -3
- data/lib/dry/logic/rule_compiler.rb +8 -40
- data/lib/dry/logic/version.rb +1 -1
- data/spec/shared/predicates.rb +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/rule/attr_spec.rb +5 -5
- data/spec/unit/rule/check_spec.rb +26 -39
- data/spec/unit/rule/conjunction_spec.rb +4 -4
- data/spec/unit/rule/disjunction_spec.rb +3 -3
- data/spec/unit/rule/each_spec.rb +2 -2
- data/spec/unit/rule/exclusive_disjunction_spec.rb +19 -0
- data/spec/unit/rule/implication_spec.rb +2 -2
- data/spec/unit/rule/key_spec.rb +103 -9
- data/spec/unit/rule/set_spec.rb +7 -9
- data/spec/unit/rule/value_spec.rb +29 -3
- data/spec/unit/rule_compiler_spec.rb +21 -49
- metadata +12 -9
- data/lib/dry/logic/rule/group.rb +0 -21
- data/lib/dry/logic/rule/result.rb +0 -33
- data/rakelib/rubocop.rake +0 -18
- data/spec/unit/rule/group_spec.rb +0 -12
- data/spec/unit/rule/result_spec.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 556cffe1d07e756e85274f2af98b8d97245e4420
|
4
|
+
data.tar.gz: 86695fbdf83593baedb7dd73483c30f41f671280
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05b63d57cfacd055926b256752698078dbca63afcf8bb03aff563cbecff5247e9dc66e999bad29cecc1fc5f37a15a5c6c76525cb1e6654f1c4915777825d5fa3
|
7
|
+
data.tar.gz: f1a65826b7049c3c5b0083b4c72feda8e911915898ff03a4d9acc5b4d6908954d9105edd36f611e535e0e0320b1f906f9ae658659a062b48ee18b564e1ab0abb
|
data/Gemfile
CHANGED
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(
|
32
|
-
|
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
|
data/lib/dry/logic/predicate.rb
CHANGED
@@ -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
|
29
|
+
def to_ast
|
30
30
|
[:predicate, [id, args]]
|
31
31
|
end
|
32
|
-
alias_method :to_a, :
|
32
|
+
alias_method :to_a, :to_ast
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/dry/logic/result.rb
CHANGED
@@ -1,155 +1,55 @@
|
|
1
1
|
module Dry
|
2
2
|
module Logic
|
3
|
-
def self.Result(
|
4
|
-
|
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, :
|
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
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
@
|
25
|
+
@input = input
|
89
26
|
end
|
90
27
|
|
91
|
-
def
|
92
|
-
|
28
|
+
def [](name)
|
29
|
+
response[name] if response.respond_to?(:[])
|
93
30
|
end
|
94
31
|
|
95
|
-
def
|
96
|
-
|
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(
|
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
|
-
@
|
41
|
+
@success
|
144
42
|
end
|
145
43
|
|
146
44
|
def failure?
|
147
|
-
!
|
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,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
|
data/lib/dry/logic/rule.rb
CHANGED
@@ -1,31 +1,15 @@
|
|
1
1
|
module Dry
|
2
2
|
module Logic
|
3
3
|
class Rule
|
4
|
-
include Dry::Equalizer(:
|
4
|
+
include Dry::Equalizer(:predicate, :options)
|
5
5
|
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :predicate
|
7
7
|
|
8
|
-
|
9
|
-
include Dry::Equalizer(:rule)
|
8
|
+
attr_reader :options
|
10
9
|
|
11
|
-
|
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
|
-
|
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(
|
48
|
+
self.class.new(predicate, options)
|
74
49
|
end
|
75
50
|
|
76
51
|
def curry(*args)
|
77
|
-
self.class.new(
|
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'
|