dry-core 0.4.9 → 0.7.1

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