dry-logic 0.3.0 → 0.4.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/.travis.yml +1 -5
- data/CHANGELOG.md +28 -0
- data/Gemfile +7 -4
- data/dry-logic.gemspec +1 -0
- data/lib/dry/logic.rb +2 -3
- data/lib/dry/logic/appliable.rb +33 -0
- data/lib/dry/logic/evaluator.rb +2 -0
- data/lib/dry/logic/operations.rb +13 -0
- data/lib/dry/logic/operations/abstract.rb +44 -0
- data/lib/dry/logic/operations/and.rb +35 -0
- data/lib/dry/logic/operations/attr.rb +17 -0
- data/lib/dry/logic/operations/binary.rb +26 -0
- data/lib/dry/logic/operations/check.rb +52 -0
- data/lib/dry/logic/operations/each.rb +32 -0
- data/lib/dry/logic/operations/implication.rb +37 -0
- data/lib/dry/logic/operations/key.rb +66 -0
- data/lib/dry/logic/operations/negation.rb +18 -0
- data/lib/dry/logic/operations/or.rb +35 -0
- data/lib/dry/logic/operations/set.rb +35 -0
- data/lib/dry/logic/operations/unary.rb +24 -0
- data/lib/dry/logic/operations/xor.rb +27 -0
- data/lib/dry/logic/operators.rb +25 -0
- data/lib/dry/logic/predicates.rb +143 -136
- data/lib/dry/logic/result.rb +76 -33
- data/lib/dry/logic/rule.rb +62 -46
- data/lib/dry/logic/rule/predicate.rb +28 -0
- data/lib/dry/logic/rule_compiler.rb +16 -17
- data/lib/dry/logic/version.rb +1 -1
- data/spec/integration/result_spec.rb +59 -0
- data/spec/integration/rule_spec.rb +53 -0
- data/spec/shared/predicates.rb +6 -0
- data/spec/shared/rule.rb +67 -0
- data/spec/spec_helper.rb +10 -3
- data/spec/support/mutant.rb +9 -0
- data/spec/unit/operations/and_spec.rb +64 -0
- data/spec/unit/operations/attr_spec.rb +27 -0
- data/spec/unit/operations/check_spec.rb +49 -0
- data/spec/unit/operations/each_spec.rb +47 -0
- data/spec/unit/operations/implication_spec.rb +30 -0
- data/spec/unit/operations/key_spec.rb +119 -0
- data/spec/unit/operations/negation_spec.rb +40 -0
- data/spec/unit/operations/or_spec.rb +73 -0
- data/spec/unit/operations/set_spec.rb +41 -0
- data/spec/unit/operations/xor_spec.rb +61 -0
- data/spec/unit/predicates_spec.rb +23 -0
- data/spec/unit/rule/predicate_spec.rb +53 -0
- data/spec/unit/rule_compiler_spec.rb +38 -38
- data/spec/unit/rule_spec.rb +94 -0
- metadata +67 -40
- data/lib/dry/logic/predicate.rb +0 -100
- data/lib/dry/logic/predicate_set.rb +0 -23
- data/lib/dry/logic/result/each.rb +0 -20
- data/lib/dry/logic/result/multi.rb +0 -14
- data/lib/dry/logic/result/named.rb +0 -17
- data/lib/dry/logic/result/set.rb +0 -10
- data/lib/dry/logic/result/value.rb +0 -17
- data/lib/dry/logic/rule/attr.rb +0 -13
- data/lib/dry/logic/rule/check.rb +0 -40
- data/lib/dry/logic/rule/composite.rb +0 -91
- data/lib/dry/logic/rule/each.rb +0 -13
- data/lib/dry/logic/rule/key.rb +0 -37
- data/lib/dry/logic/rule/negation.rb +0 -15
- data/lib/dry/logic/rule/set.rb +0 -31
- data/lib/dry/logic/rule/value.rb +0 -48
- data/spec/unit/predicate_spec.rb +0 -115
- data/spec/unit/rule/attr_spec.rb +0 -29
- data/spec/unit/rule/check_spec.rb +0 -44
- data/spec/unit/rule/conjunction_spec.rb +0 -30
- data/spec/unit/rule/disjunction_spec.rb +0 -38
- data/spec/unit/rule/each_spec.rb +0 -31
- data/spec/unit/rule/exclusive_disjunction_spec.rb +0 -19
- data/spec/unit/rule/implication_spec.rb +0 -16
- data/spec/unit/rule/key_spec.rb +0 -121
- data/spec/unit/rule/set_spec.rb +0 -30
- data/spec/unit/rule/value_spec.rb +0 -99
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'dry/logic/operations/unary'
|
2
|
+
require 'dry/logic/result'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Logic
|
6
|
+
module Operations
|
7
|
+
class Negation < Unary
|
8
|
+
def type
|
9
|
+
:not
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(input)
|
13
|
+
Result.new(!rule[input], id) { ast(input) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'dry/logic/operations/binary'
|
2
|
+
require 'dry/logic/result'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Logic
|
6
|
+
module Operations
|
7
|
+
class Or < Binary
|
8
|
+
def type
|
9
|
+
:or
|
10
|
+
end
|
11
|
+
alias_method :operator, :type
|
12
|
+
|
13
|
+
def call(input)
|
14
|
+
left_result = left.(input)
|
15
|
+
|
16
|
+
if left_result.success?
|
17
|
+
Result::SUCCESS
|
18
|
+
else
|
19
|
+
right_result = right.(input)
|
20
|
+
|
21
|
+
if right_result.success?
|
22
|
+
Result::SUCCESS
|
23
|
+
else
|
24
|
+
Result.new(false, id) { [:or, [left_result.to_ast, right_result.to_ast]] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](input)
|
30
|
+
left[input] || right[input]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'dry/logic/operations/abstract'
|
2
|
+
require 'dry/logic/result'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Logic
|
6
|
+
module Operations
|
7
|
+
class Set < Abstract
|
8
|
+
def type
|
9
|
+
:set
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(input)
|
13
|
+
results = rules.map { |rule| rule.(input) }
|
14
|
+
success = results.all?(&:success?)
|
15
|
+
|
16
|
+
Result.new(success, id) do
|
17
|
+
[type, results.select(&:failure?).map { |failure| failure.to_ast }]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](input)
|
22
|
+
rules.map { |rule| rule[input] }.all?
|
23
|
+
end
|
24
|
+
|
25
|
+
def ast(input = Undefined)
|
26
|
+
[type, rules.map { |rule| rule.ast(input) }]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{type}(#{rules.map(&:to_s).join(', ')})"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dry/logic/operations/abstract'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Logic
|
5
|
+
module Operations
|
6
|
+
class Unary < Abstract
|
7
|
+
attr_reader :rule
|
8
|
+
|
9
|
+
def initialize(*rules, **options)
|
10
|
+
super
|
11
|
+
@rule = rules.first
|
12
|
+
end
|
13
|
+
|
14
|
+
def ast(input = Undefined)
|
15
|
+
[type, rule.ast(input)]
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{type}(#{rule})"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'dry/logic/operations/binary'
|
2
|
+
require 'dry/logic/result'
|
3
|
+
|
4
|
+
module Dry
|
5
|
+
module Logic
|
6
|
+
module Operations
|
7
|
+
class Xor < Binary
|
8
|
+
def type
|
9
|
+
:xor
|
10
|
+
end
|
11
|
+
alias_method :operator, :type
|
12
|
+
|
13
|
+
def call(input)
|
14
|
+
Result.new(self[input], id) { ast(input) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](input)
|
18
|
+
left[input] ^ right[input]
|
19
|
+
end
|
20
|
+
|
21
|
+
def ast(input = Undefined)
|
22
|
+
[type, rules.map { |rule| rule.ast(input) }]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Dry
|
2
|
+
module Logic
|
3
|
+
module Operators
|
4
|
+
def and(other)
|
5
|
+
Operations::And.new(self, other)
|
6
|
+
end
|
7
|
+
alias & and
|
8
|
+
|
9
|
+
def or(other)
|
10
|
+
Operations::Or.new(self, other)
|
11
|
+
end
|
12
|
+
alias | or
|
13
|
+
|
14
|
+
def xor(other)
|
15
|
+
Operations::Xor.new(self, other)
|
16
|
+
end
|
17
|
+
alias ^ xor
|
18
|
+
|
19
|
+
def then(other)
|
20
|
+
Operations::Implication.new(self, other)
|
21
|
+
end
|
22
|
+
alias > then
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/dry/logic/predicates.rb
CHANGED
@@ -2,189 +2,196 @@ require 'bigdecimal'
|
|
2
2
|
require 'bigdecimal/util'
|
3
3
|
require 'date'
|
4
4
|
|
5
|
-
require 'dry/logic/predicate_set'
|
6
|
-
|
7
5
|
module Dry
|
8
6
|
module Logic
|
9
7
|
module Predicates
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
other.extend(Logic::PredicateSet)
|
15
|
-
other.import(self)
|
16
|
-
end
|
8
|
+
module Methods
|
9
|
+
def [](name)
|
10
|
+
method(name)
|
11
|
+
end
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
def type?(type, input)
|
14
|
+
input.kind_of?(type)
|
15
|
+
end
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
def none?(input)
|
18
|
+
input.nil?
|
19
|
+
end
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
def key?(name, input)
|
22
|
+
input.key?(name)
|
23
|
+
end
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
def attr?(name, input)
|
26
|
+
input.respond_to?(name)
|
27
|
+
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
def empty?(input)
|
30
|
+
case input
|
31
|
+
when String, Array, Hash then input.empty?
|
32
|
+
when nil then true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
40
36
|
end
|
41
|
-
end
|
42
37
|
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
def filled?(input)
|
39
|
+
!self[:empty?].(input)
|
40
|
+
end
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
def bool?(input)
|
43
|
+
input.is_a?(TrueClass) || input.is_a?(FalseClass)
|
44
|
+
end
|
50
45
|
|
51
|
-
|
52
|
-
|
53
|
-
|
46
|
+
def date?(input)
|
47
|
+
input.is_a?(Date)
|
48
|
+
end
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
50
|
+
def date_time?(input)
|
51
|
+
input.is_a?(DateTime)
|
52
|
+
end
|
58
53
|
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
def time?(input)
|
55
|
+
input.is_a?(Time)
|
56
|
+
end
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
def number?(input)
|
59
|
+
begin
|
60
|
+
true if Float(input)
|
61
|
+
rescue ArgumentError, TypeError
|
62
|
+
false
|
63
|
+
end
|
68
64
|
end
|
69
|
-
end
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
def int?(input)
|
67
|
+
input.is_a?(Fixnum)
|
68
|
+
end
|
74
69
|
|
75
|
-
|
76
|
-
|
77
|
-
|
70
|
+
def float?(input)
|
71
|
+
input.is_a?(Float)
|
72
|
+
end
|
78
73
|
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
def decimal?(input)
|
75
|
+
input.is_a?(BigDecimal)
|
76
|
+
end
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
def str?(input)
|
79
|
+
input.is_a?(String)
|
80
|
+
end
|
86
81
|
|
87
|
-
|
88
|
-
|
89
|
-
|
82
|
+
def hash?(input)
|
83
|
+
input.is_a?(Hash)
|
84
|
+
end
|
90
85
|
|
91
|
-
|
92
|
-
|
93
|
-
|
86
|
+
def array?(input)
|
87
|
+
input.is_a?(Array)
|
88
|
+
end
|
94
89
|
|
95
|
-
|
96
|
-
|
97
|
-
|
90
|
+
def odd?(input)
|
91
|
+
input.odd?
|
92
|
+
end
|
98
93
|
|
99
|
-
|
100
|
-
|
101
|
-
|
94
|
+
def even?(input)
|
95
|
+
input.even?
|
96
|
+
end
|
102
97
|
|
103
|
-
|
104
|
-
|
105
|
-
|
98
|
+
def lt?(num, input)
|
99
|
+
input < num
|
100
|
+
end
|
106
101
|
|
107
|
-
|
108
|
-
|
109
|
-
|
102
|
+
def gt?(num, input)
|
103
|
+
input > num
|
104
|
+
end
|
110
105
|
|
111
|
-
|
112
|
-
|
113
|
-
|
106
|
+
def lteq?(num, input)
|
107
|
+
!self[:gt?].(num, input)
|
108
|
+
end
|
114
109
|
|
115
|
-
|
116
|
-
|
117
|
-
|
110
|
+
def gteq?(num, input)
|
111
|
+
!self[:lt?].(num, input)
|
112
|
+
end
|
118
113
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
114
|
+
def size?(size, input)
|
115
|
+
case size
|
116
|
+
when Fixnum then size == input.size
|
117
|
+
when Range, Array then size.include?(input.size)
|
118
|
+
else
|
119
|
+
raise ArgumentError, "+#{size}+ is not supported type for size? predicate."
|
120
|
+
end
|
125
121
|
end
|
126
|
-
end
|
127
122
|
|
128
|
-
|
129
|
-
|
130
|
-
|
123
|
+
def min_size?(num, input)
|
124
|
+
input.size >= num
|
125
|
+
end
|
131
126
|
|
132
|
-
|
133
|
-
|
134
|
-
|
127
|
+
def max_size?(num, input)
|
128
|
+
input.size <= num
|
129
|
+
end
|
135
130
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
131
|
+
def inclusion?(list, input)
|
132
|
+
::Kernel.warn 'inclusion is deprecated - use included_in instead.'
|
133
|
+
self[:included_in?].(list, input)
|
134
|
+
end
|
140
135
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
136
|
+
def exclusion?(list, input)
|
137
|
+
::Kernel.warn 'exclusion is deprecated - use excluded_from instead.'
|
138
|
+
self[:excluded_from?].(list, input)
|
139
|
+
end
|
145
140
|
|
146
|
-
|
147
|
-
|
148
|
-
|
141
|
+
def included_in?(list, input)
|
142
|
+
list.include?(input)
|
143
|
+
end
|
149
144
|
|
150
|
-
|
151
|
-
|
152
|
-
|
145
|
+
def excluded_from?(list, input)
|
146
|
+
!list.include?(input)
|
147
|
+
end
|
153
148
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
149
|
+
def includes?(value, input)
|
150
|
+
begin
|
151
|
+
if input.respond_to?(:include?)
|
152
|
+
input.include?(value)
|
153
|
+
else
|
154
|
+
false
|
155
|
+
end
|
156
|
+
rescue TypeError
|
159
157
|
false
|
160
158
|
end
|
161
|
-
rescue TypeError
|
162
|
-
false
|
163
159
|
end
|
164
|
-
end
|
165
160
|
|
166
|
-
|
167
|
-
|
168
|
-
|
161
|
+
def excludes?(value, input)
|
162
|
+
!self[:includes?].(value, input)
|
163
|
+
end
|
169
164
|
|
170
|
-
|
171
|
-
|
172
|
-
|
165
|
+
def eql?(left, right)
|
166
|
+
left.eql?(right)
|
167
|
+
end
|
173
168
|
|
174
|
-
|
175
|
-
|
176
|
-
|
169
|
+
def not_eql?(left, right)
|
170
|
+
!left.eql?(right)
|
171
|
+
end
|
177
172
|
|
178
|
-
|
179
|
-
|
180
|
-
|
173
|
+
def true?(value)
|
174
|
+
value.equal?(true)
|
175
|
+
end
|
181
176
|
|
182
|
-
|
183
|
-
|
177
|
+
def false?(value)
|
178
|
+
value.equal?(false)
|
179
|
+
end
|
180
|
+
|
181
|
+
def format?(regex, input)
|
182
|
+
!regex.match(input).nil?
|
183
|
+
end
|
184
|
+
|
185
|
+
def predicate(name, &block)
|
186
|
+
define_singleton_method(name, &block)
|
187
|
+
end
|
184
188
|
end
|
185
189
|
|
186
|
-
|
187
|
-
|
190
|
+
extend Methods
|
191
|
+
|
192
|
+
def self.included(other)
|
193
|
+
super
|
194
|
+
other.extend(Methods)
|
188
195
|
end
|
189
196
|
end
|
190
197
|
end
|