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
data/lib/dry/logic/rule/attr.rb
CHANGED
@@ -1,21 +1,13 @@
|
|
1
1
|
module Dry
|
2
2
|
module Logic
|
3
|
-
class Rule::Attr < Rule
|
4
|
-
def self.
|
5
|
-
|
3
|
+
class Rule::Attr < Rule::Key
|
4
|
+
def self.evaluator(options)
|
5
|
+
Evaluator::Attr.new(options.fetch(:name))
|
6
6
|
end
|
7
7
|
|
8
8
|
def type
|
9
9
|
:attr
|
10
10
|
end
|
11
|
-
|
12
|
-
def evaluate_input(input)
|
13
|
-
input.public_send(name)
|
14
|
-
end
|
15
|
-
|
16
|
-
def call(input)
|
17
|
-
Logic::Result::LazyValue.new(input, predicate.(input), self)
|
18
|
-
end
|
19
11
|
end
|
20
12
|
end
|
21
13
|
end
|
data/lib/dry/logic/rule/check.rb
CHANGED
@@ -1,39 +1,40 @@
|
|
1
|
+
require 'dry/logic/evaluator'
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Logic
|
3
|
-
class Rule::Check < Rule
|
4
|
-
attr_reader :
|
5
|
+
class Rule::Check < Rule::Value
|
6
|
+
attr_reader :name, :evaluator
|
7
|
+
|
8
|
+
def self.new(predicate, options)
|
9
|
+
keys = options.fetch(:keys)
|
10
|
+
evaluator = Evaluator::Set.new(keys)
|
5
11
|
|
6
|
-
|
7
|
-
def evaluate_input(*)
|
8
|
-
predicate.input
|
9
|
-
end
|
12
|
+
super(predicate, options.merge(evaluator: evaluator))
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
parent, child = key.to_a.flatten
|
17
|
-
result[parent].input[child]
|
18
|
-
else
|
19
|
-
result[key].input
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
15
|
+
def initialize(predicate, options)
|
16
|
+
super
|
17
|
+
@name = options.fetch(:name)
|
18
|
+
@evaluator = options[:evaluator]
|
23
19
|
end
|
24
20
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
21
|
+
def call(input)
|
22
|
+
args = evaluator[input].reverse
|
23
|
+
*head, tail = args
|
24
|
+
Logic.Result(predicate.curry(*head).(tail), head.size > 0 ? curry(*head) : self, input)
|
28
25
|
end
|
29
26
|
|
30
|
-
def
|
31
|
-
|
27
|
+
def evaluate(input)
|
28
|
+
evaluator[input].first
|
32
29
|
end
|
33
30
|
|
34
31
|
def type
|
35
32
|
:check
|
36
33
|
end
|
34
|
+
|
35
|
+
def to_ast
|
36
|
+
[type, [name, predicate.to_ast]]
|
37
|
+
end
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
@@ -3,7 +3,7 @@ module Dry
|
|
3
3
|
class Rule::Composite < Rule
|
4
4
|
include Dry::Equalizer(:left, :right)
|
5
5
|
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :left, :right
|
7
7
|
|
8
8
|
def initialize(left, right)
|
9
9
|
@left = left
|
@@ -14,15 +14,19 @@ module Dry
|
|
14
14
|
:"#{left.name}_#{type}_#{right.name}"
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
[type, [left.
|
17
|
+
def to_ast
|
18
|
+
[type, [left.to_ast, right.to_ast]]
|
19
19
|
end
|
20
|
-
alias_method :to_a, :
|
20
|
+
alias_method :to_a, :to_ast
|
21
21
|
end
|
22
22
|
|
23
23
|
class Rule::Implication < Rule::Composite
|
24
|
-
def call(
|
25
|
-
left.(
|
24
|
+
def call(input)
|
25
|
+
if left.(input).success?
|
26
|
+
right.(input)
|
27
|
+
else
|
28
|
+
Logic.Result(true, left, input)
|
29
|
+
end
|
26
30
|
end
|
27
31
|
|
28
32
|
def type
|
@@ -31,8 +35,14 @@ module Dry
|
|
31
35
|
end
|
32
36
|
|
33
37
|
class Rule::Conjunction < Rule::Composite
|
34
|
-
def call(
|
35
|
-
left.(
|
38
|
+
def call(input)
|
39
|
+
result = left.(input)
|
40
|
+
|
41
|
+
if result.success?
|
42
|
+
right.(input)
|
43
|
+
else
|
44
|
+
result
|
45
|
+
end
|
36
46
|
end
|
37
47
|
|
38
48
|
def type
|
@@ -41,8 +51,14 @@ module Dry
|
|
41
51
|
end
|
42
52
|
|
43
53
|
class Rule::Disjunction < Rule::Composite
|
44
|
-
def call(
|
45
|
-
left.(
|
54
|
+
def call(input)
|
55
|
+
result = left.(input)
|
56
|
+
|
57
|
+
if result.success?
|
58
|
+
result
|
59
|
+
else
|
60
|
+
right.(input)
|
61
|
+
end
|
46
62
|
end
|
47
63
|
|
48
64
|
def type
|
@@ -51,8 +67,12 @@ module Dry
|
|
51
67
|
end
|
52
68
|
|
53
69
|
class Rule::ExclusiveDisjunction < Rule::Composite
|
54
|
-
def call(
|
55
|
-
left.(
|
70
|
+
def call(input)
|
71
|
+
Logic.Result(left.(input).success? ^ right.(input).success?, self, input)
|
72
|
+
end
|
73
|
+
|
74
|
+
def evaluate(input)
|
75
|
+
[left.evaluate(input), right.evaluate(input)]
|
56
76
|
end
|
57
77
|
|
58
78
|
def type
|
data/lib/dry/logic/rule/each.rb
CHANGED
data/lib/dry/logic/rule/key.rb
CHANGED
@@ -1,16 +1,35 @@
|
|
1
|
+
require 'dry/logic/evaluator'
|
2
|
+
|
1
3
|
module Dry
|
2
4
|
module Logic
|
3
|
-
class Rule::Key < Rule
|
4
|
-
|
5
|
-
|
5
|
+
class Rule::Key < Rule::Value
|
6
|
+
attr_reader :name, :evaluator
|
7
|
+
|
8
|
+
def self.new(predicate, options)
|
9
|
+
name = options.fetch(:name)
|
10
|
+
super(predicate, evaluator: evaluator(options), name: name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.evaluator(options)
|
14
|
+
Evaluator::Key.new(options.fetch(:name))
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(predicate, options)
|
18
|
+
super
|
19
|
+
@name = options[:name]
|
20
|
+
@evaluator = options[:evaluator]
|
21
|
+
end
|
22
|
+
|
23
|
+
def evaluate(input)
|
24
|
+
evaluator[input]
|
6
25
|
end
|
7
26
|
|
8
27
|
def type
|
9
28
|
:key
|
10
29
|
end
|
11
30
|
|
12
|
-
def
|
13
|
-
|
31
|
+
def to_ast
|
32
|
+
[type, [name, predicate.to_ast]]
|
14
33
|
end
|
15
34
|
end
|
16
35
|
end
|
data/lib/dry/logic/rule/set.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
module Dry
|
2
2
|
module Logic
|
3
|
-
class Rule::Set < Rule
|
4
|
-
|
5
|
-
Logic.Result(input, predicate.map { |rule| rule.(input) }, self)
|
6
|
-
end
|
3
|
+
class Rule::Set < Rule::Value
|
4
|
+
alias_method :rules, :predicate
|
7
5
|
|
8
6
|
def type
|
9
7
|
:set
|
10
8
|
end
|
11
9
|
|
10
|
+
def apply(input)
|
11
|
+
rules.map { |rule| rule.(input) }
|
12
|
+
end
|
13
|
+
|
12
14
|
def at(*args)
|
13
|
-
|
15
|
+
new(rules.values_at(*args))
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
[type,
|
18
|
+
def to_ast
|
19
|
+
[type, rules.map { |rule| rule.to_ast }]
|
18
20
|
end
|
19
|
-
alias_method :to_a, :to_ary
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
data/lib/dry/logic/rule/value.rb
CHANGED
@@ -1,12 +1,24 @@
|
|
1
1
|
module Dry
|
2
2
|
module Logic
|
3
3
|
class Rule::Value < Rule
|
4
|
+
def type
|
5
|
+
:val
|
6
|
+
end
|
7
|
+
|
4
8
|
def call(input)
|
5
|
-
Logic.Result(
|
9
|
+
Logic.Result(apply(input), self, input)
|
6
10
|
end
|
7
11
|
|
8
|
-
def
|
9
|
-
|
12
|
+
def apply(input)
|
13
|
+
predicate.(evaluate(input))
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate(input)
|
17
|
+
input
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_ast
|
21
|
+
[type, predicate.to_ast]
|
10
22
|
end
|
11
23
|
end
|
12
24
|
end
|
@@ -10,7 +10,7 @@ module Dry
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def call(ast)
|
13
|
-
ast.
|
13
|
+
ast.map { |node| visit(node) }
|
14
14
|
end
|
15
15
|
|
16
16
|
def visit(node)
|
@@ -20,26 +20,7 @@ module Dry
|
|
20
20
|
|
21
21
|
def visit_check(node)
|
22
22
|
name, predicate, keys = node
|
23
|
-
|
24
|
-
klass = check_keys.size == 1 ? Rule::Check::Unary : Rule::Check::Binary
|
25
|
-
klass.new(name, visit(predicate), check_keys)
|
26
|
-
end
|
27
|
-
|
28
|
-
def visit_res(node)
|
29
|
-
name, predicate = node
|
30
|
-
Rule::Result.new(name, visit(predicate))
|
31
|
-
end
|
32
|
-
|
33
|
-
def visit_args(nodes)
|
34
|
-
nodes.map { |node| visit(node) }
|
35
|
-
end
|
36
|
-
|
37
|
-
def visit_res_arg(name)
|
38
|
-
predicates[name].input
|
39
|
-
end
|
40
|
-
|
41
|
-
def visit_arg(value)
|
42
|
-
value
|
23
|
+
Rule::Check.new(visit(predicate), name: name, keys: keys || [name])
|
43
24
|
end
|
44
25
|
|
45
26
|
def visit_not(node)
|
@@ -48,37 +29,29 @@ module Dry
|
|
48
29
|
|
49
30
|
def visit_key(node)
|
50
31
|
name, predicate = node
|
51
|
-
Rule::Key.new(
|
32
|
+
Rule::Key.new(visit(predicate), name: name)
|
52
33
|
end
|
53
34
|
|
54
35
|
def visit_attr(node)
|
55
36
|
name, predicate = node
|
56
|
-
Rule::Attr.new(
|
37
|
+
Rule::Attr.new(visit(predicate), name: name)
|
57
38
|
end
|
58
39
|
|
59
40
|
def visit_val(node)
|
60
|
-
|
61
|
-
Rule::Value.new(name, visit(predicate))
|
41
|
+
Rule::Value.new(visit(node))
|
62
42
|
end
|
63
43
|
|
64
44
|
def visit_set(node)
|
65
|
-
|
66
|
-
Rule::Set.new(name, call(rules))
|
45
|
+
Rule::Set.new(call(node))
|
67
46
|
end
|
68
47
|
|
69
48
|
def visit_each(node)
|
70
|
-
|
71
|
-
Rule::Each.new(name, visit(rule))
|
49
|
+
Rule::Each.new(visit(node))
|
72
50
|
end
|
73
51
|
|
74
52
|
def visit_predicate(node)
|
75
53
|
name, args = node
|
76
|
-
|
77
|
-
if args[0] == :args
|
78
|
-
predicates[name].curry(*visit(args))
|
79
|
-
else
|
80
|
-
predicates[name].curry(*args)
|
81
|
-
end
|
54
|
+
predicates[name].curry(*args)
|
82
55
|
end
|
83
56
|
|
84
57
|
def visit_and(node)
|
@@ -100,11 +73,6 @@ module Dry
|
|
100
73
|
left, right = node
|
101
74
|
visit(left) > visit(right)
|
102
75
|
end
|
103
|
-
|
104
|
-
def visit_group(node)
|
105
|
-
identifier, predicate = node
|
106
|
-
Rule::Group.new(identifier, visit(predicate))
|
107
|
-
end
|
108
76
|
end
|
109
77
|
end
|
110
78
|
end
|
data/lib/dry/logic/version.rb
CHANGED
data/spec/shared/predicates.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/unit/rule/attr_spec.rb
CHANGED
@@ -5,24 +5,24 @@ RSpec.describe Dry::Logic::Rule::Attr do
|
|
5
5
|
|
6
6
|
let(:model) { Struct.new(:name) }
|
7
7
|
|
8
|
-
subject(:rule) { described_class.new(:name
|
8
|
+
subject(:rule) { described_class.new(str?, name: :name) }
|
9
9
|
|
10
10
|
describe '#call' do
|
11
11
|
it 'applies predicate to the value' do
|
12
|
-
expect(rule.(model.new(
|
13
|
-
expect(rule.(nil)).to be_failure
|
12
|
+
expect(rule.(model.new('Jane'))).to be_success
|
13
|
+
expect(rule.(model.new(nil))).to be_failure
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
describe '#and' do
|
18
|
-
let(:other) { Dry::Logic::Rule::
|
18
|
+
let(:other) { Dry::Logic::Rule::Attr.new(min_size?.curry(3), name: :name) }
|
19
19
|
|
20
20
|
it 'returns conjunction rule where value is passed to the right' do
|
21
21
|
present_and_string = rule.and(other)
|
22
22
|
|
23
23
|
expect(present_and_string.(model.new('Jane'))).to be_success
|
24
24
|
|
25
|
-
expect(present_and_string.(
|
25
|
+
expect(present_and_string.(model.new('Ja'))).to be_failure
|
26
26
|
expect(present_and_string.(model.new(1))).to be_failure
|
27
27
|
end
|
28
28
|
end
|