dry-core 0.4.7 → 0.6.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.
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