dry-logic 0.3.0 → 0.4.0

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