dry-core 0.4.9 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dry-core.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core'
3
+ require "dry/core"
data/lib/dry/core.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/version'
3
+ require "dry/core/version"
4
4
 
5
5
  # :nodoc:
6
6
  module Dry
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/map'
3
+ require "concurrent/map"
4
4
 
5
5
  module Dry
6
6
  module Core
@@ -46,7 +46,8 @@ module Dry
46
46
  # this means you shouldn't pass Procs in args unless you're sure
47
47
  # they are always the same instances, otherwise you introduce a memory leak
48
48
  #
49
- # @return [Object] block's return value (cached for subsequent calls with the same argument values)
49
+ # @return [Object] block's return value (cached for subsequent calls with
50
+ # the same argument values)
50
51
  def fetch_or_store(*args, &block)
51
52
  cache.fetch_or_store(args.hash, &block)
52
53
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
4
- require 'dry/core/errors'
3
+ require "dry/core/constants"
4
+ require "dry/core/errors"
5
5
 
6
6
  module Dry
7
7
  module Core
@@ -51,22 +51,42 @@ module Dry
51
51
  # defines :one, :two, type: Dry::Types['strict.int']
52
52
  # end
53
53
  #
54
- def defines(*args, type: Object)
55
- 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) # rubocop:disable Metrics/PerceivedComplexity
71
+ unless coerce.respond_to?(:call)
72
+ raise ::ArgumentError, "Non-callable coerce option: #{coerce.inspect}"
73
+ end
74
+
75
+ mod = ::Module.new do
56
76
  args.each do |name|
57
- define_method(name) do |value = Undefined|
58
- ivar = "@#{name}"
77
+ ivar = :"@#{name}"
59
78
 
60
- if value == Undefined
79
+ define_method(name) do |value = Undefined|
80
+ if Undefined.equal?(value)
61
81
  if instance_variable_defined?(ivar)
62
82
  instance_variable_get(ivar)
63
- else
83
+ else # rubocop:disable Style/EmptyElse
64
84
  nil
65
85
  end
86
+ elsif type === value # rubocop:disable Style/CaseEquality
87
+ instance_variable_set(ivar, coerce.call(value))
66
88
  else
67
- raise InvalidClassAttributeValue.new(name, value) unless type === value
68
-
69
- instance_variable_set(ivar, value)
89
+ raise InvalidClassAttributeValue.new(name, value)
70
90
  end
71
91
  end
72
92
  end
@@ -6,9 +6,7 @@ module Dry
6
6
  class ClassBuilder
7
7
  ParentClassMismatch = Class.new(TypeError)
8
8
 
9
- attr_reader :name
10
- attr_reader :parent
11
- attr_reader :namespace
9
+ attr_reader :name, :parent, :namespace
12
10
 
13
11
  def initialize(name:, parent: nil, namespace: nil)
14
12
  @name = name
@@ -72,8 +70,6 @@ module Dry
72
70
  remove_const(name)
73
71
  const_set(name, klass)
74
72
 
75
- const_get(name).name if RUBY_VERSION < '2.4'
76
-
77
73
  remove_const(name)
78
74
  const_set(name, base)
79
75
  end
@@ -85,14 +81,14 @@ module Dry
85
81
  def create_base(namespace, name, parent)
86
82
  begin
87
83
  namespace.const_get(name)
88
- rescue NameError
84
+ rescue NameError # rubocop:disable Lint/SuppressedException
89
85
  end
90
86
 
91
87
  if namespace.const_defined?(name, false)
92
88
  existing = namespace.const_get(name)
93
89
 
94
90
  unless existing <= parent
95
- raise ParentClassMismatch, "#{ existing.name } must be a subclass of #{ parent.name }"
91
+ raise ParentClassMismatch, "#{existing.name} must be a subclass of #{parent.name}"
96
92
  end
97
93
 
98
94
  existing
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
3
+ require "set"
4
4
 
5
5
  module Dry
6
6
  module Core
@@ -23,16 +23,18 @@ module Dry
23
23
  # An empty list of options
24
24
  EMPTY_OPTS = {}.freeze
25
25
  # An empty set
26
- EMPTY_SET = Set.new.freeze
26
+ EMPTY_SET = ::Set.new.freeze
27
27
  # An empty string
28
- EMPTY_STRING = ''.freeze
28
+ EMPTY_STRING = ""
29
+ # Identity function
30
+ IDENTITY = (-> x { x }).freeze
29
31
 
30
32
  # A special value you can use as a default to know if no arguments
31
- # were passed to you method
33
+ # were passed to the method
32
34
  #
33
35
  # @example
34
36
  # def method(value = Undefined)
35
- # if value == Undefined
37
+ # if Undefined.equal?(value)
36
38
  # puts 'no args'
37
39
  # else
38
40
  # puts value
@@ -40,16 +42,16 @@ module Dry
40
42
  # end
41
43
  Undefined = Object.new.tap do |undefined|
42
44
  # @api private
43
- Self = -> { Undefined }
45
+ Self = -> { Undefined } # rubocop:disable Lint/ConstantDefinitionInBlock
44
46
 
45
47
  # @api public
46
48
  def undefined.to_s
47
- 'Undefined'
49
+ "Undefined"
48
50
  end
49
51
 
50
52
  # @api public
51
53
  def undefined.inspect
52
- 'Undefined'
54
+ "Undefined"
53
55
  end
54
56
 
55
57
  # Pick a value, if the first argument is not Undefined, return it back,
@@ -60,7 +62,7 @@ module Dry
60
62
  # 1 + Undefined.default(val, 2)
61
63
  # end
62
64
  #
63
- def undefined.default(x, y = self)
65
+ def undefined.default(x, y = self) # rubocop:disable Naming/MethodParameterName
64
66
  if equal?(x)
65
67
  if equal?(y)
66
68
  yield
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
3
+ require "logger"
4
4
 
5
5
  module Dry
6
6
  module Core
@@ -17,7 +17,7 @@ module Dry
17
17
  # def old_api; end
18
18
  # def new_api; end
19
19
  #
20
- # deprecate_method :old_api, :new_api, "old_api is no-no"
20
+ # deprecate :old_api, :new_api, message: "old_api is no-no"
21
21
  # end
22
22
  #
23
23
  # @example You also can use this module for your custom messages
@@ -33,24 +33,34 @@ module Dry
33
33
  # Prints a warning
34
34
  #
35
35
  # @param [String] msg Warning string
36
- def warn(msg, tag: nil)
37
- tagged = "[#{tag || 'deprecated'}] #{msg.gsub(/^\s+/, '')}"
38
- 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_locations(uplevel + 2, 1)[0]} "
41
+ tag = "[#{tag || "deprecated"}] "
42
+ hint = msg.gsub(/^\s+/, "")
43
+
44
+ logger.warn("#{caller_info}#{tag}#{hint}")
39
45
  end
40
46
 
41
47
  # Wraps arguments with a standard message format and prints a warning
42
48
  #
43
49
  # @param [Object] name what is deprecated
44
50
  # @param [String] msg additional message usually containing upgrade instructions
45
- def announce(name, msg, tag: nil)
46
- warn(deprecation_message(name, msg), tag: tag)
51
+ def announce(name, msg, tag: nil, uplevel: nil)
52
+ # Bump the uplevel (if provided) by one to account for the uplevel calculation
53
+ # taking place one frame deeper in `.warn`
54
+ uplevel += 1 if uplevel
55
+
56
+ warn(deprecation_message(name, msg), tag: tag, uplevel: uplevel)
47
57
  end
48
58
 
49
59
  # @api private
50
60
  def deprecation_message(name, msg)
51
61
  <<-MSG
52
- #{ name } is deprecated and will be removed in the next major version
53
- #{ msg }
62
+ #{name} is deprecated and will be removed in the next major version
63
+ #{msg}
54
64
  MSG
55
65
  end
56
66
 
@@ -92,12 +102,12 @@ module Dry
92
102
  # @param [#warn] logger
93
103
  #
94
104
  # @api public
95
- def set_logger!(output = $stderr)
105
+ def set_logger!(output = $stderr) # rubocop:disable Naming/AccessorMethodName
96
106
  if output.respond_to?(:warn)
97
107
  @logger = output
98
108
  else
99
109
  @logger = Logger.new(output).tap do |logger|
100
- logger.formatter = proc { |_, _, _, msg| "#{ msg }\n" }
110
+ logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
101
111
  end
102
112
  end
103
113
  end
@@ -108,8 +118,9 @@ module Dry
108
118
  end
109
119
 
110
120
  # @api private
111
- class Tagged < Module
121
+ class Tagged < ::Module
112
122
  def initialize(tag)
123
+ super()
113
124
  @tag = tag
114
125
  end
115
126
 
@@ -145,8 +156,8 @@ module Dry
145
156
  # @option [String] message optional deprecation message
146
157
  def deprecate(old_name, new_name = nil, message: nil)
147
158
  full_msg = Deprecations.deprecated_name_message(
148
- "#{self.name}##{old_name}",
149
- new_name ? "#{self.name}##{new_name}" : nil,
159
+ "#{name}##{old_name}",
160
+ new_name ? "#{name}##{new_name}" : nil,
150
161
  message
151
162
  )
152
163
  mod = self
@@ -155,7 +166,7 @@ module Dry
155
166
  undef_method old_name if method_defined?(old_name)
156
167
 
157
168
  define_method(old_name) do |*args, &block|
158
- mod.warn("#{ full_msg }\n#{ STACK.() }")
169
+ mod.warn("#{full_msg}\n#{STACK.()}")
159
170
  __send__(new_name, *args, &block)
160
171
  end
161
172
  else
@@ -165,7 +176,7 @@ module Dry
165
176
  undef_method old_name
166
177
 
167
178
  define_method(old_name) do |*args, &block|
168
- mod.warn("#{ full_msg }\n#{ STACK.() }")
179
+ mod.warn("#{full_msg}\n#{STACK.()}")
169
180
  __send__(aliased_name, *args, &block)
170
181
  end
171
182
  end
@@ -178,8 +189,8 @@ module Dry
178
189
  # @option [String] message optional deprecation message
179
190
  def deprecate_class_method(old_name, new_name = nil, message: nil)
180
191
  full_msg = Deprecations.deprecated_name_message(
181
- "#{self.name}.#{old_name}",
182
- new_name ? "#{self.name}.#{new_name}" : nil,
192
+ "#{name}.#{old_name}",
193
+ new_name ? "#{name}.#{new_name}" : nil,
183
194
  message
184
195
  )
185
196
 
@@ -189,7 +200,7 @@ module Dry
189
200
  undef_method old_name if method_defined?(old_name)
190
201
 
191
202
  define_method(old_name) do |*args, &block|
192
- warn("#{ full_msg }\n#{ STACK.() }")
203
+ warn("#{full_msg}\n#{STACK.()}")
193
204
  meth.call(*args, &block)
194
205
  end
195
206
  end
@@ -203,14 +214,14 @@ module Dry
203
214
  remove_const(constant_name)
204
215
 
205
216
  full_msg = Deprecations.deprecated_name_message(
206
- "#{self.name}::#{constant_name}",
217
+ "#{name}::#{constant_name}",
207
218
  message
208
219
  )
209
220
 
210
221
  mod = Module.new do
211
222
  define_method(:const_missing) do |missing|
212
223
  if missing == constant_name
213
- warn("#{ full_msg }\n#{ STACK.() }")
224
+ warn("#{full_msg}\n#{STACK.()}")
214
225
  value
215
226
  else
216
227
  super(missing)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/array'
3
+ require "concurrent/array"
4
4
 
5
5
  module Dry
6
6
  module Core
@@ -0,0 +1,152 @@
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
+ super()
33
+ @keys = keys.uniq
34
+ define_methods(**options)
35
+ freeze
36
+ end
37
+
38
+ private
39
+
40
+ # Hook called when module is included
41
+ #
42
+ # @param [Module] descendant
43
+ # the module or class including Equalizer
44
+ #
45
+ # @return [self]
46
+ #
47
+ # @api private
48
+ def included(descendant)
49
+ super
50
+ descendant.include Methods
51
+ end
52
+
53
+ # Define the equalizer methods based on #keys
54
+ #
55
+ # @param [Boolean] inspect whether to define #inspect method
56
+ # @param [Boolean] immutable whether to memoize #hash method
57
+ #
58
+ # @return [undefined]
59
+ #
60
+ # @api private
61
+ def define_methods(inspect: true, immutable: false)
62
+ define_cmp_method
63
+ define_hash_method(immutable: immutable)
64
+ define_inspect_method if inspect
65
+ end
66
+
67
+ # Define an #cmp? method based on the instance's values identified by #keys
68
+ #
69
+ # @return [undefined]
70
+ #
71
+ # @api private
72
+ def define_cmp_method
73
+ keys = @keys
74
+ define_method(:cmp?) do |comparator, other|
75
+ keys.all? do |key|
76
+ __send__(key).public_send(comparator, other.__send__(key))
77
+ end
78
+ end
79
+ private :cmp?
80
+ end
81
+
82
+ # Define a #hash method based on the instance's values identified by #keys
83
+ #
84
+ # @return [undefined]
85
+ #
86
+ # @api private
87
+ def define_hash_method(immutable:)
88
+ calculate_hash = ->(obj) { @keys.map { |key| obj.__send__(key) }.push(obj.class).hash }
89
+ if immutable
90
+ define_method(:hash) do
91
+ @__hash__ ||= calculate_hash.call(self)
92
+ end
93
+ define_method(:freeze) do
94
+ hash
95
+ super()
96
+ end
97
+ else
98
+ define_method(:hash) do
99
+ calculate_hash.call(self)
100
+ end
101
+ end
102
+ end
103
+
104
+ # Define an inspect method that reports the values of the instance's keys
105
+ #
106
+ # @return [undefined]
107
+ #
108
+ # @api private
109
+ def define_inspect_method
110
+ keys = @keys
111
+ define_method(:inspect) do
112
+ klass = self.class
113
+ name = klass.name || klass.inspect
114
+ "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
115
+ end
116
+ end
117
+
118
+ # The comparison methods
119
+ module Methods
120
+ # Compare the object with other object for equality
121
+ #
122
+ # @example
123
+ # object.eql?(other) # => true or false
124
+ #
125
+ # @param [Object] other
126
+ # the other object to compare with
127
+ #
128
+ # @return [Boolean]
129
+ #
130
+ # @api public
131
+ def eql?(other)
132
+ instance_of?(other.class) && cmp?(__method__, other)
133
+ end
134
+
135
+ # Compare the object with other object for equivalency
136
+ #
137
+ # @example
138
+ # object == other # => true or false
139
+ #
140
+ # @param [Object] other
141
+ # the other object to compare with
142
+ #
143
+ # @return [Boolean]
144
+ #
145
+ # @api public
146
+ def ==(other)
147
+ other.is_a?(self.class) && cmp?(__method__, other)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end