dry-logic 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|