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.
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