dry-logic 1.0.2 → 1.0.8

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -26
  3. data/LICENSE +1 -1
  4. data/README.md +12 -14
  5. data/dry-logic.gemspec +27 -14
  6. data/lib/dry-logic.rb +1 -1
  7. data/lib/dry/logic.rb +2 -2
  8. data/lib/dry/logic/evaluator.rb +1 -1
  9. data/lib/dry/logic/operations.rb +11 -11
  10. data/lib/dry/logic/operations/abstract.rb +6 -6
  11. data/lib/dry/logic/operations/and.rb +4 -4
  12. data/lib/dry/logic/operations/attr.rb +1 -1
  13. data/lib/dry/logic/operations/binary.rb +1 -1
  14. data/lib/dry/logic/operations/check.rb +4 -4
  15. data/lib/dry/logic/operations/each.rb +2 -2
  16. data/lib/dry/logic/operations/implication.rb +2 -2
  17. data/lib/dry/logic/operations/key.rb +5 -5
  18. data/lib/dry/logic/operations/negation.rb +2 -2
  19. data/lib/dry/logic/operations/or.rb +2 -2
  20. data/lib/dry/logic/operations/set.rb +3 -3
  21. data/lib/dry/logic/operations/unary.rb +1 -1
  22. data/lib/dry/logic/operations/xor.rb +2 -2
  23. data/lib/dry/logic/operators.rb +4 -4
  24. data/lib/dry/logic/predicates.rb +60 -28
  25. data/lib/dry/logic/result.rb +2 -2
  26. data/lib/dry/logic/rule.rb +11 -11
  27. data/lib/dry/logic/rule/interface.rb +32 -37
  28. data/lib/dry/logic/rule/predicate.rb +3 -3
  29. data/lib/dry/logic/rule_compiler.rb +3 -3
  30. data/lib/dry/logic/version.rb +1 -1
  31. metadata +12 -133
  32. data/.codeclimate.yml +0 -15
  33. data/.gitignore +0 -9
  34. data/.rspec +0 -3
  35. data/.travis.yml +0 -31
  36. data/CONTRIBUTING.md +0 -29
  37. data/Gemfile +0 -15
  38. data/Rakefile +0 -14
  39. data/benchmarks/rule_application.rb +0 -30
  40. data/benchmarks/setup.rb +0 -13
  41. data/bin/console +0 -11
  42. data/examples/basic.rb +0 -16
  43. data/spec/integration/result_spec.rb +0 -61
  44. data/spec/integration/rule_spec.rb +0 -55
  45. data/spec/shared/predicates.rb +0 -59
  46. data/spec/shared/rule.rb +0 -74
  47. data/spec/spec_helper.rb +0 -36
  48. data/spec/support/mutant.rb +0 -11
  49. data/spec/unit/operations/and_spec.rb +0 -70
  50. data/spec/unit/operations/attr_spec.rb +0 -29
  51. data/spec/unit/operations/check_spec.rb +0 -51
  52. data/spec/unit/operations/each_spec.rb +0 -49
  53. data/spec/unit/operations/implication_spec.rb +0 -32
  54. data/spec/unit/operations/key_spec.rb +0 -135
  55. data/spec/unit/operations/negation_spec.rb +0 -51
  56. data/spec/unit/operations/or_spec.rb +0 -75
  57. data/spec/unit/operations/set_spec.rb +0 -43
  58. data/spec/unit/operations/xor_spec.rb +0 -63
  59. data/spec/unit/predicates/array_spec.rb +0 -43
  60. data/spec/unit/predicates/attr_spec.rb +0 -31
  61. data/spec/unit/predicates/bool_spec.rb +0 -36
  62. data/spec/unit/predicates/case_spec.rb +0 -35
  63. data/spec/unit/predicates/date_spec.rb +0 -33
  64. data/spec/unit/predicates/date_time_spec.rb +0 -33
  65. data/spec/unit/predicates/decimal_spec.rb +0 -34
  66. data/spec/unit/predicates/empty_spec.rb +0 -40
  67. data/spec/unit/predicates/eql_spec.rb +0 -23
  68. data/spec/unit/predicates/even_spec.rb +0 -33
  69. data/spec/unit/predicates/excluded_from_spec.rb +0 -37
  70. data/spec/unit/predicates/excludes_spec.rb +0 -58
  71. data/spec/unit/predicates/false_spec.rb +0 -37
  72. data/spec/unit/predicates/filled_spec.rb +0 -40
  73. data/spec/unit/predicates/float_spec.rb +0 -33
  74. data/spec/unit/predicates/format_spec.rb +0 -23
  75. data/spec/unit/predicates/gt_spec.rb +0 -42
  76. data/spec/unit/predicates/gteq_spec.rb +0 -42
  77. data/spec/unit/predicates/included_in_spec.rb +0 -37
  78. data/spec/unit/predicates/includes_spec.rb +0 -24
  79. data/spec/unit/predicates/int_spec.rb +0 -36
  80. data/spec/unit/predicates/key_spec.rb +0 -31
  81. data/spec/unit/predicates/lt_spec.rb +0 -42
  82. data/spec/unit/predicates/lteq_spec.rb +0 -42
  83. data/spec/unit/predicates/max_size_spec.rb +0 -51
  84. data/spec/unit/predicates/min_size_spec.rb +0 -51
  85. data/spec/unit/predicates/none_spec.rb +0 -30
  86. data/spec/unit/predicates/not_eql_spec.rb +0 -23
  87. data/spec/unit/predicates/number_spec.rb +0 -39
  88. data/spec/unit/predicates/odd_spec.rb +0 -33
  89. data/spec/unit/predicates/respond_to_spec.rb +0 -31
  90. data/spec/unit/predicates/size_spec.rb +0 -57
  91. data/spec/unit/predicates/str_spec.rb +0 -34
  92. data/spec/unit/predicates/time_spec.rb +0 -33
  93. data/spec/unit/predicates/true_spec.rb +0 -37
  94. data/spec/unit/predicates/type_spec.rb +0 -37
  95. data/spec/unit/predicates/uuid_v4_spec.rb +0 -29
  96. data/spec/unit/predicates_spec.rb +0 -25
  97. data/spec/unit/rule/predicate_spec.rb +0 -55
  98. data/spec/unit/rule_compiler_spec.rb +0 -129
  99. data/spec/unit/rule_spec.rb +0 -213
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/binary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/binary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -9,7 +9,7 @@ module Dry
9
9
  class And < Binary
10
10
  attr_reader :hints
11
11
 
12
- def initialize(*)
12
+ def initialize(*, **)
13
13
  super
14
14
  @hints = options.fetch(:hints, true)
15
15
  end
@@ -17,7 +17,7 @@ module Dry
17
17
  def type
18
18
  :and
19
19
  end
20
- alias operator type
20
+ alias_method :operator, :type
21
21
 
22
22
  def call(input)
23
23
  left_result = left.(input)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/key'
3
+ require "dry/logic/operations/key"
4
4
 
5
5
  module Dry
6
6
  module Logic
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/abstract'
3
+ require "dry/logic/operations/abstract"
4
4
 
5
5
  module Dry
6
6
  module Logic
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/unary'
4
- require 'dry/logic/evaluator'
5
- require 'dry/logic/result'
3
+ require "dry/logic/operations/unary"
4
+ require "dry/logic/evaluator"
5
+ require "dry/logic/result"
6
6
 
7
7
  module Dry
8
8
  module Logic
@@ -17,7 +17,7 @@ module Dry
17
17
  keys = options.fetch(:keys)
18
18
  evaluator = Evaluator::Set.new(keys)
19
19
 
20
- super(rule, options.merge(evaluator: evaluator))
20
+ super(rule, **options, evaluator: evaluator)
21
21
  end
22
22
  end
23
23
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/unary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/unary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/binary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/binary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/unary'
4
- require 'dry/logic/evaluator'
5
- require 'dry/logic/result'
3
+ require "dry/logic/operations/unary"
4
+ require "dry/logic/evaluator"
5
+ require "dry/logic/result"
6
6
 
7
7
  module Dry
8
8
  module Logic
@@ -12,13 +12,13 @@ module Dry
12
12
 
13
13
  attr_reader :path
14
14
 
15
- def self.new(rules, options)
15
+ def self.new(rules, **options)
16
16
  if options[:evaluator]
17
17
  super
18
18
  else
19
19
  name = options.fetch(:name)
20
20
  eval = options.fetch(:evaluator, evaluator(name))
21
- super(rules, options.merge(evaluator: eval, path: name))
21
+ super(rules, **options, evaluator: eval, path: name)
22
22
  end
23
23
  end
24
24
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/unary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/unary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/binary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/binary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/abstract'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/abstract"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -29,7 +29,7 @@ module Dry
29
29
  end
30
30
 
31
31
  def to_s
32
- "#{type}(#{rules.map(&:to_s).join(', ')})"
32
+ "#{type}(#{rules.map(&:to_s).join(", ")})"
33
33
  end
34
34
  end
35
35
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/abstract'
3
+ require "dry/logic/operations/abstract"
4
4
 
5
5
  module Dry
6
6
  module Logic
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/logic/operations/binary'
4
- require 'dry/logic/result'
3
+ require "dry/logic/operations/binary"
4
+ require "dry/logic/result"
5
5
 
6
6
  module Dry
7
7
  module Logic
@@ -6,22 +6,22 @@ module Dry
6
6
  def and(other)
7
7
  Operations::And.new(self, other)
8
8
  end
9
- alias & and
9
+ alias_method :&, :and
10
10
 
11
11
  def or(other)
12
12
  Operations::Or.new(self, other)
13
13
  end
14
- alias | or
14
+ alias_method :|, :or
15
15
 
16
16
  def xor(other)
17
17
  Operations::Xor.new(self, other)
18
18
  end
19
- alias ^ xor
19
+ alias_method :^, :xor
20
20
 
21
21
  def then(other)
22
22
  Operations::Implication.new(self, other)
23
23
  end
24
- alias > then
24
+ alias_method :>, :then
25
25
  end
26
26
  end
27
27
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bigdecimal'
4
- require 'bigdecimal/util'
5
- require 'date'
3
+ require "bigdecimal"
4
+ require "bigdecimal/util"
5
+ require "date"
6
6
 
7
7
  module Dry
8
8
  module Logic
@@ -13,7 +13,7 @@ module Dry
13
13
  end
14
14
 
15
15
  def type?(type, input)
16
- input.kind_of?(type)
16
+ input.is_a?(type)
17
17
  end
18
18
 
19
19
  def nil?(input)
@@ -59,11 +59,9 @@ module Dry
59
59
  end
60
60
 
61
61
  def number?(input)
62
- begin
63
- true if Float(input)
64
- rescue ArgumentError, TypeError
65
- false
66
- end
62
+ true if Float(input)
63
+ rescue ArgumentError, TypeError
64
+ false
67
65
  end
68
66
 
69
67
  def int?(input)
@@ -116,7 +114,7 @@ module Dry
116
114
 
117
115
  def size?(size, input)
118
116
  case size
119
- when Integer then size == input.size
117
+ when Integer then size.equal?(input.size)
120
118
  when Range, Array then size.include?(input.size)
121
119
  else
122
120
  raise ArgumentError, "+#{size}+ is not supported type for size? predicate."
@@ -131,13 +129,30 @@ module Dry
131
129
  input.size <= num
132
130
  end
133
131
 
132
+ def bytesize?(size, input)
133
+ case size
134
+ when Integer then size.equal?(input.bytesize)
135
+ when Range, Array then size.include?(input.bytesize)
136
+ else
137
+ raise ArgumentError, "+#{size}+ is not supported type for bytesize? predicate."
138
+ end
139
+ end
140
+
141
+ def min_bytesize?(num, input)
142
+ input.bytesize >= num
143
+ end
144
+
145
+ def max_bytesize?(num, input)
146
+ input.bytesize <= num
147
+ end
148
+
134
149
  def inclusion?(list, input)
135
- ::Kernel.warn 'inclusion is deprecated - use included_in instead.'
150
+ ::Kernel.warn "inclusion is deprecated - use included_in instead."
136
151
  included_in?(list, input)
137
152
  end
138
153
 
139
154
  def exclusion?(list, input)
140
- ::Kernel.warn 'exclusion is deprecated - use excluded_from instead.'
155
+ ::Kernel.warn "exclusion is deprecated - use excluded_from instead."
141
156
  excluded_from?(list, input)
142
157
  end
143
158
 
@@ -150,15 +165,13 @@ module Dry
150
165
  end
151
166
 
152
167
  def includes?(value, input)
153
- begin
154
- if input.respond_to?(:include?)
155
- input.include?(value)
156
- else
157
- false
158
- end
159
- rescue TypeError
168
+ if input.respond_to?(:include?)
169
+ input.include?(value)
170
+ else
160
171
  false
161
172
  end
173
+ rescue TypeError
174
+ false
162
175
  end
163
176
 
164
177
  def excludes?(value, input)
@@ -185,25 +198,44 @@ module Dry
185
198
  value.equal?(false)
186
199
  end
187
200
 
188
- if RUBY_VERSION < '2.4'
189
- def format?(regex, input)
190
- !regex.match(input).nil?
191
- end
192
- else
193
- def format?(regex, input)
194
- regex.match?(input)
195
- end
201
+ def format?(regex, input)
202
+ !input.nil? && regex.match?(input)
196
203
  end
197
204
 
198
205
  def case?(pattern, input)
199
206
  pattern === input
200
207
  end
201
208
 
209
+ def uuid_v1?(input)
210
+ uuid_v1_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
211
+ format?(uuid_v1_format, input)
212
+ end
213
+
214
+ def uuid_v2?(input)
215
+ uuid_v2_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
216
+ format?(uuid_v2_format, input)
217
+ end
218
+
219
+ def uuid_v3?(input)
220
+ uuid_v3_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
221
+ format?(uuid_v3_format, input)
222
+ end
223
+
202
224
  def uuid_v4?(input)
203
- uuid_v4_format = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
225
+ uuid_v4_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
204
226
  format?(uuid_v4_format, input)
205
227
  end
206
228
 
229
+ def uuid_v5?(input)
230
+ uuid_v5_format = /\A[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i
231
+ format?(uuid_v5_format, input)
232
+ end
233
+
234
+ def uri?(schemes, input)
235
+ uri_format = URI::DEFAULT_PARSER.make_regexp(schemes)
236
+ format?(uri_format, input)
237
+ end
238
+
207
239
  def respond_to?(method, input)
208
240
  input.respond_to?(method)
209
241
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
3
+ require "dry/core/constants"
4
4
 
5
5
  module Dry
6
6
  module Logic
@@ -69,7 +69,7 @@ module Dry
69
69
  if args.empty?
70
70
  name.to_s
71
71
  else
72
- "#{name}(#{args.map(&:last).map(&:inspect).join(', ')})"
72
+ "#{name}(#{args.map(&:last).map(&:inspect).join(", ")})"
73
73
  end
74
74
  end
75
75
 
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
4
- require 'dry/core/constants'
5
- require 'dry/equalizer'
6
- require 'dry/logic/operations'
7
- require 'dry/logic/result'
8
- require 'dry/logic/rule/interface'
3
+ require "concurrent/map"
4
+ require "dry/core/constants"
5
+ require "dry/equalizer"
6
+ require "dry/logic/operations"
7
+ require "dry/logic/result"
8
+ require "dry/logic/rule/interface"
9
9
 
10
10
  module Dry
11
11
  module Logic
@@ -38,13 +38,13 @@ module Dry
38
38
  base.interfaces.fetch_or_store([arity, curried]) do
39
39
  interface = Interface.new(arity, curried)
40
40
  klass = Class.new(base) { include interface }
41
- base.const_set("#{base.name.split('::').last}#{interface.name}", klass)
41
+ base.const_set("#{base.name.split("::").last}#{interface.name}", klass)
42
42
  klass
43
43
  end
44
44
  end
45
45
 
46
46
  def self.build(predicate, args: EMPTY_ARRAY, arity: predicate.arity, **options)
47
- specialize(arity, args.size).new(predicate, { args: args, arity: arity, **options })
47
+ specialize(arity, args.size).new(predicate, {args: args, arity: arity, **options})
48
48
  end
49
49
 
50
50
  def initialize(predicate, options = EMPTY_HASH)
@@ -68,11 +68,11 @@ module Dry
68
68
 
69
69
  def bind(object)
70
70
  if predicate.respond_to?(:bind)
71
- self.class.build(predicate.bind(object), options)
71
+ self.class.build(predicate.bind(object), **options)
72
72
  else
73
73
  self.class.build(
74
74
  -> *args { object.instance_exec(*args, &predicate) },
75
- options.merge(arity: arity, parameters: parameters)
75
+ **options, arity: arity, parameters: parameters
76
76
  )
77
77
  end
78
78
  end
@@ -82,7 +82,7 @@ module Dry
82
82
  end
83
83
 
84
84
  def with(new_opts)
85
- self.class.build(predicate, options.merge(new_opts))
85
+ self.class.build(predicate, **options, **new_opts)
86
86
  end
87
87
 
88
88
  def parameters
@@ -4,6 +4,8 @@ module Dry
4
4
  module Logic
5
5
  class Rule
6
6
  class Interface < ::Module
7
+ SPLAT = ["*rest"].freeze
8
+
7
9
  attr_reader :arity
8
10
 
9
11
  attr_reader :curried
@@ -18,12 +20,10 @@ module Dry
18
20
 
19
21
  define_constructor if curried?
20
22
 
21
- if variable_arity?
22
- define_splat_application
23
- elsif constant?
23
+ if constant?
24
24
  define_constant_application
25
25
  else
26
- define_fixed_application
26
+ define_application
27
27
  end
28
28
  end
29
29
 
@@ -32,7 +32,7 @@ module Dry
32
32
  end
33
33
 
34
34
  def variable_arity?
35
- arity.equal?(-1)
35
+ arity.negative?
36
36
  end
37
37
 
38
38
  def curried?
@@ -41,7 +41,13 @@ module Dry
41
41
 
42
42
  def unapplied
43
43
  if variable_arity?
44
- -1
44
+ unapplied = arity.abs - 1 - curried
45
+
46
+ if unapplied.negative?
47
+ 0
48
+ else
49
+ unapplied
50
+ end
45
51
  else
46
52
  arity - curried
47
53
  end
@@ -49,10 +55,21 @@ module Dry
49
55
 
50
56
  def name
51
57
  if constant?
52
- 'Constant'
58
+ "Constant"
53
59
  else
54
- arity_str = variable_arity? ? 'VariableArity' : "#{arity}Arity"
55
- curried_str = curried? ? "#{curried}Curried" : EMPTY_STRING
60
+ arity_str =
61
+ if variable_arity?
62
+ "Variable#{arity.abs - 1}Arity"
63
+ else
64
+ "#{arity}Arity"
65
+ end
66
+
67
+ curried_str =
68
+ if curried?
69
+ "#{curried}Curried"
70
+ else
71
+ EMPTY_STRING
72
+ end
56
73
 
57
74
  "#{arity_str}#{curried_str}"
58
75
  end
@@ -61,9 +78,9 @@ module Dry
61
78
  def define_constructor
62
79
  assignment =
63
80
  if curried.equal?(1)
64
- '@arg0 = @args[0]'
81
+ "@arg0 = @args[0]"
65
82
  else
66
- "#{curried_args.join(', ')} = @args"
83
+ "#{curried_args.join(", ")} = @args"
67
84
  end
68
85
 
69
86
  module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
@@ -91,32 +108,10 @@ module Dry
91
108
  end
92
109
  end
93
110
 
94
- def define_splat_application
95
- application =
96
- if curried?
97
- "@predicate[#{curried_args.join(', ')}, *input]"
98
- else
99
- '@predicate[*input]'
100
- end
101
-
102
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
103
- def call(*input)
104
- if #{application}
105
- Result::SUCCESS
106
- else
107
- Result.new(false, id) { ast(*input) }
108
- end
109
- end
110
-
111
- def [](*input)
112
- #{application}
113
- end
114
- RUBY
115
- end
116
-
117
- def define_fixed_application
118
- parameters = unapplied_args.join(', ')
119
- application = "@predicate[#{ (curried_args + unapplied_args).join(', ') }]"
111
+ def define_application
112
+ splat = variable_arity? ? SPLAT : EMPTY_ARRAY
113
+ parameters = (unapplied_args + splat).join(", ")
114
+ application = "@predicate[#{(curried_args + unapplied_args + splat).join(", ")}]"
120
115
 
121
116
  module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
122
117
  def call(#{parameters})