dry-core 0.4.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dry-core.rb CHANGED
@@ -1 +1,3 @@
1
- require 'dry/core'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core"
data/lib/dry/core.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'dry/core/version'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/version"
2
4
 
3
5
  # :nodoc:
4
6
  module Dry
@@ -1,4 +1,6 @@
1
- require 'concurrent/map'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
2
4
 
3
5
  module Dry
4
6
  module Core
@@ -1,5 +1,7 @@
1
- require 'dry/core/constants'
2
- require 'dry/core/errors'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/constants"
4
+ require "dry/core/errors"
3
5
 
4
6
  module Dry
5
7
  module Core
@@ -49,22 +51,42 @@ module Dry
49
51
  # defines :one, :two, type: Dry::Types['strict.int']
50
52
  # end
51
53
  #
52
- def defines(*args, type: Object)
53
- mod = Module.new do
54
+ # @example with coercion using Proc
55
+ #
56
+ # class Bar
57
+ # extend Dry::Core::ClassAttributes
58
+ #
59
+ # defines :one, coerce: proc { |value| value.to_s }
60
+ # end
61
+ #
62
+ # @example with coercion using dry-types
63
+ #
64
+ # class Bar
65
+ # extend Dry::Core::ClassAttributes
66
+ #
67
+ # defines :one, coerce: Dry::Types['coercible.string']
68
+ # end
69
+ #
70
+ def defines(*args, type: ::Object, coerce: IDENTITY)
71
+ unless coerce.respond_to?(:call)
72
+ raise ::ArgumentError, "Non-callable coerce option: #{coerce.inspect}"
73
+ end
74
+
75
+ mod = ::Module.new do
54
76
  args.each do |name|
55
- define_method(name) do |value = Undefined|
56
- ivar = "@#{name}"
77
+ ivar = :"@#{name}"
57
78
 
58
- if value == Undefined
79
+ define_method(name) do |value = Undefined|
80
+ if Undefined.equal?(value)
59
81
  if instance_variable_defined?(ivar)
60
82
  instance_variable_get(ivar)
61
83
  else
62
84
  nil
63
85
  end
86
+ elsif type === value
87
+ instance_variable_set(ivar, coerce.call(value))
64
88
  else
65
- raise InvalidClassAttributeValue.new(name, value) unless type === value
66
-
67
- instance_variable_set(ivar, value)
89
+ raise InvalidClassAttributeValue.new(name, value)
68
90
  end
69
91
  end
70
92
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Core
3
5
  # Class for generating more classes
@@ -70,8 +72,6 @@ module Dry
70
72
  remove_const(name)
71
73
  const_set(name, klass)
72
74
 
73
- const_get(name).name if RUBY_VERSION < '2.4'
74
-
75
75
  remove_const(name)
76
76
  const_set(name, base)
77
77
  end
@@ -90,7 +90,7 @@ module Dry
90
90
  existing = namespace.const_get(name)
91
91
 
92
92
  unless existing <= parent
93
- raise ParentClassMismatch, "#{ existing.name } must be a subclass of #{ parent.name }"
93
+ raise ParentClassMismatch, "#{existing.name} must be a subclass of #{parent.name}"
94
94
  end
95
95
 
96
96
  existing
@@ -1,4 +1,6 @@
1
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
2
4
 
3
5
  module Dry
4
6
  module Core
@@ -6,6 +8,7 @@ module Dry
6
8
  #
7
9
  # @example Just include this module to your class or module
8
10
  # class Foo
11
+ # include Dry::Core::Constants
9
12
  # def call(value = EMPTY_ARRAY)
10
13
  # value.map(&:to_s)
11
14
  # end
@@ -20,41 +23,48 @@ module Dry
20
23
  # An empty list of options
21
24
  EMPTY_OPTS = {}.freeze
22
25
  # An empty set
23
- EMPTY_SET = Set.new.freeze
26
+ EMPTY_SET = ::Set.new.freeze
24
27
  # An empty string
25
- EMPTY_STRING = ''.freeze
28
+ EMPTY_STRING = "".freeze
29
+ # Identity function
30
+ IDENTITY = (-> x { x }).freeze
26
31
 
27
32
  # A special value you can use as a default to know if no arguments
28
- # were passed to you method
33
+ # were passed to the method
29
34
  #
30
35
  # @example
31
36
  # def method(value = Undefined)
32
- # if value == Undefined
37
+ # if Undefined.equal?(value)
33
38
  # puts 'no args'
34
39
  # else
35
40
  # puts value
36
41
  # end
37
42
  # end
38
43
  Undefined = Object.new.tap do |undefined|
44
+ # @api private
45
+ Self = -> { Undefined }
46
+
47
+ # @api public
39
48
  def undefined.to_s
40
- 'Undefined'
49
+ "Undefined"
41
50
  end
42
51
 
52
+ # @api public
43
53
  def undefined.inspect
44
- 'Undefined'
54
+ "Undefined"
45
55
  end
46
56
 
47
57
  # Pick a value, if the first argument is not Undefined, return it back,
48
58
  # otherwise return the second arg or yield the block.
49
59
  #
50
60
  # @example
51
- # def method(val = Undefined)
52
- # 1 + Undefined.default(val, 2)
53
- # end
61
+ # def method(val = Undefined)
62
+ # 1 + Undefined.default(val, 2)
63
+ # end
54
64
  #
55
65
  def undefined.default(x, y = self)
56
- if x.equal?(self)
57
- if y.equal?(self)
66
+ if equal?(x)
67
+ if equal?(y)
58
68
  yield
59
69
  else
60
70
  y
@@ -63,6 +73,36 @@ module Dry
63
73
  x
64
74
  end
65
75
  end
76
+
77
+ # Map a non-undefined value
78
+ #
79
+ # @example
80
+ # def add_five(val = Undefined)
81
+ # Undefined.map(val) { |x| x + 5 }
82
+ # end
83
+ #
84
+ def undefined.map(value)
85
+ if equal?(value)
86
+ self
87
+ else
88
+ yield(value)
89
+ end
90
+ end
91
+
92
+ # @api public
93
+ def undefined.dup
94
+ self
95
+ end
96
+
97
+ # @api public
98
+ def undefined.clone
99
+ self
100
+ end
101
+
102
+ # @api public
103
+ def undefined.coalesce(*args)
104
+ args.find(Self) { |x| !equal?(x) }
105
+ end
66
106
  end.freeze
67
107
 
68
108
  def self.included(base)
@@ -1,4 +1,6 @@
1
- require 'logger'
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
2
4
 
3
5
  module Dry
4
6
  module Core
@@ -15,7 +17,7 @@ module Dry
15
17
  # def old_api; end
16
18
  # def new_api; end
17
19
  #
18
- # deprecate_method :old_api, :new_api, "old_api is no-no"
20
+ # deprecate :old_api, :new_api, message: "old_api is no-no"
19
21
  # end
20
22
  #
21
23
  # @example You also can use this module for your custom messages
@@ -31,24 +33,31 @@ module Dry
31
33
  # Prints a warning
32
34
  #
33
35
  # @param [String] msg Warning string
34
- def warn(msg, tag: nil)
35
- tagged = "[#{tag || 'deprecated'}] #{msg.gsub(/^\s+/, '')}"
36
- logger.warn(tagged)
36
+ # @param [String] tag Tag to help identify the source of the warning.
37
+ # Defaults to "deprecated"
38
+ # @param [Integer] Caller frame to add to the message
39
+ def warn(msg, tag: nil, uplevel: nil)
40
+ caller_info = uplevel.nil? ? nil : caller[uplevel]
41
+ tag = "[#{tag || "deprecated"}]"
42
+ hint = msg.gsub(/^\s+/, "")
43
+ logger.warn(
44
+ [caller_info, tag, hint].compact.join(" ")
45
+ )
37
46
  end
38
47
 
39
48
  # Wraps arguments with a standard message format and prints a warning
40
49
  #
41
50
  # @param [Object] name what is deprecated
42
51
  # @param [String] msg additional message usually containing upgrade instructions
43
- def announce(name, msg, tag: nil)
44
- warn(deprecation_message(name, msg), tag: tag)
52
+ def announce(name, msg, tag: nil, uplevel: nil)
53
+ warn(deprecation_message(name, msg), tag: tag, uplevel: uplevel)
45
54
  end
46
55
 
47
56
  # @api private
48
57
  def deprecation_message(name, msg)
49
58
  <<-MSG
50
- #{ name } is deprecated and will be removed in the next major version
51
- #{ msg }
59
+ #{name} is deprecated and will be removed in the next major version
60
+ #{msg}
52
61
  MSG
53
62
  end
54
63
 
@@ -95,7 +104,7 @@ module Dry
95
104
  @logger = output
96
105
  else
97
106
  @logger = Logger.new(output).tap do |logger|
98
- logger.formatter = proc { |_, _, _, msg| "#{ msg }\n" }
107
+ logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
99
108
  end
100
109
  end
101
110
  end
@@ -153,7 +162,7 @@ module Dry
153
162
  undef_method old_name if method_defined?(old_name)
154
163
 
155
164
  define_method(old_name) do |*args, &block|
156
- mod.warn("#{ full_msg }\n#{ STACK.() }")
165
+ mod.warn("#{full_msg}\n#{STACK.()}")
157
166
  __send__(new_name, *args, &block)
158
167
  end
159
168
  else
@@ -163,7 +172,7 @@ module Dry
163
172
  undef_method old_name
164
173
 
165
174
  define_method(old_name) do |*args, &block|
166
- mod.warn("#{ full_msg }\n#{ STACK.() }")
175
+ mod.warn("#{full_msg}\n#{STACK.()}")
167
176
  __send__(aliased_name, *args, &block)
168
177
  end
169
178
  end
@@ -187,7 +196,7 @@ module Dry
187
196
  undef_method old_name if method_defined?(old_name)
188
197
 
189
198
  define_method(old_name) do |*args, &block|
190
- warn("#{ full_msg }\n#{ STACK.() }")
199
+ warn("#{full_msg}\n#{STACK.()}")
191
200
  meth.call(*args, &block)
192
201
  end
193
202
  end
@@ -208,7 +217,7 @@ module Dry
208
217
  mod = Module.new do
209
218
  define_method(:const_missing) do |missing|
210
219
  if missing == constant_name
211
- warn("#{ full_msg }\n#{ STACK.() }")
220
+ warn("#{full_msg}\n#{STACK.()}")
212
221
  value
213
222
  else
214
223
  super(missing)
@@ -1,4 +1,6 @@
1
- require 'concurrent/array'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/array"
2
4
 
3
5
  module Dry
4
6
  module Core
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ # Build an equalizer module for the inclusion in other class
5
+ #
6
+ # ## Credits
7
+ #
8
+ # Equalizer has been originally imported from the equalizer gem created by Dan Kubb
9
+ #
10
+ # @api public
11
+ def self.Equalizer(*keys, **options)
12
+ Dry::Core::Equalizer.new(*keys, **options)
13
+ end
14
+
15
+ module Core
16
+ # Define equality, equivalence and inspection methods
17
+ class Equalizer < Module
18
+ # Initialize an Equalizer with the given keys
19
+ #
20
+ # Will use the keys with which it is initialized to define #cmp?,
21
+ # #hash, and #inspect
22
+ #
23
+ # @param [Array<Symbol>] keys
24
+ # @param [Hash] options
25
+ # @option options [Boolean] :inspect whether to define #inspect method
26
+ # @option options [Boolean] :immutable whether to memoize #hash method
27
+ #
28
+ # @return [undefined]
29
+ #
30
+ # @api private
31
+ def initialize(*keys, **options)
32
+ @keys = keys.uniq
33
+ define_methods(**options)
34
+ freeze
35
+ end
36
+
37
+ private
38
+
39
+ # Hook called when module is included
40
+ #
41
+ # @param [Module] descendant
42
+ # the module or class including Equalizer
43
+ #
44
+ # @return [self]
45
+ #
46
+ # @api private
47
+ def included(descendant)
48
+ super
49
+ descendant.include Methods
50
+ end
51
+
52
+ # Define the equalizer methods based on #keys
53
+ #
54
+ # @param [Boolean] inspect whether to define #inspect method
55
+ # @param [Boolean] immutable whether to memoize #hash method
56
+ #
57
+ # @return [undefined]
58
+ #
59
+ # @api private
60
+ def define_methods(inspect: true, immutable: false)
61
+ define_cmp_method
62
+ define_hash_method(immutable: immutable)
63
+ define_inspect_method if inspect
64
+ end
65
+
66
+ # Define an #cmp? method based on the instance's values identified by #keys
67
+ #
68
+ # @return [undefined]
69
+ #
70
+ # @api private
71
+ def define_cmp_method
72
+ keys = @keys
73
+ define_method(:cmp?) do |comparator, other|
74
+ keys.all? do |key|
75
+ __send__(key).public_send(comparator, other.__send__(key))
76
+ end
77
+ end
78
+ private :cmp?
79
+ end
80
+
81
+ # Define a #hash method based on the instance's values identified by #keys
82
+ #
83
+ # @return [undefined]
84
+ #
85
+ # @api private
86
+ def define_hash_method(immutable:)
87
+ calculate_hash = ->(obj) { @keys.map { |key| obj.__send__(key) }.push(obj.class).hash }
88
+ if immutable
89
+ define_method(:hash) do
90
+ @__hash__ ||= calculate_hash.call(self)
91
+ end
92
+ define_method(:freeze) do
93
+ hash
94
+ super()
95
+ end
96
+ else
97
+ define_method(:hash) do
98
+ calculate_hash.call(self)
99
+ end
100
+ end
101
+ end
102
+
103
+ # Define an inspect method that reports the values of the instance's keys
104
+ #
105
+ # @return [undefined]
106
+ #
107
+ # @api private
108
+ def define_inspect_method
109
+ keys = @keys
110
+ define_method(:inspect) do
111
+ klass = self.class
112
+ name = klass.name || klass.inspect
113
+ "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
114
+ end
115
+ end
116
+
117
+ # The comparison methods
118
+ module Methods
119
+ # Compare the object with other object for equality
120
+ #
121
+ # @example
122
+ # object.eql?(other) # => true or false
123
+ #
124
+ # @param [Object] other
125
+ # the other object to compare with
126
+ #
127
+ # @return [Boolean]
128
+ #
129
+ # @api public
130
+ def eql?(other)
131
+ instance_of?(other.class) && cmp?(__method__, other)
132
+ end
133
+
134
+ # Compare the object with other object for equivalency
135
+ #
136
+ # @example
137
+ # object == other # => true or false
138
+ #
139
+ # @param [Object] other
140
+ # the other object to compare with
141
+ #
142
+ # @return [Boolean]
143
+ #
144
+ # @api public
145
+ def ==(other)
146
+ other.is_a?(self.class) && cmp?(__method__, other)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end