dry-core 0.4.6 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +154 -41
- data/LICENSE +20 -0
- data/README.md +18 -36
- data/dry-core.gemspec +25 -23
- data/lib/dry-core.rb +3 -1
- data/lib/dry/core.rb +3 -1
- data/lib/dry/core/cache.rb +3 -1
- data/lib/dry/core/class_attributes.rb +32 -10
- data/lib/dry/core/class_builder.rb +3 -3
- data/lib/dry/core/constants.rb +52 -12
- data/lib/dry/core/deprecations.rb +12 -10
- data/lib/dry/core/descendants_tracker.rb +3 -1
- data/lib/dry/core/equalizer.rb +151 -0
- data/lib/dry/core/errors.rb +2 -0
- data/lib/dry/core/extensions.rb +3 -1
- data/lib/dry/core/inflector.rb +6 -3
- data/lib/dry/core/memoizable.rb +2 -0
- data/lib/dry/core/version.rb +3 -1
- metadata +25 -30
- data/.gitignore +0 -10
- data/.inch.yml +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -31
- data/.travis.yml +0 -31
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -22
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -6
data/lib/dry-core.rb
CHANGED
data/lib/dry/core.rb
CHANGED
data/lib/dry/core/cache.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
53
|
-
|
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
|
-
|
56
|
-
ivar = "@#{name}"
|
77
|
+
ivar = :"@#{name}"
|
57
78
|
|
58
|
-
|
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)
|
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, "#{
|
93
|
+
raise ParentClassMismatch, "#{existing.name} must be a subclass of #{parent.name}"
|
94
94
|
end
|
95
95
|
|
96
96
|
existing
|
data/lib/dry/core/constants.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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 =
|
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
|
33
|
+
# were passed to the method
|
29
34
|
#
|
30
35
|
# @example
|
31
36
|
# def method(value = Undefined)
|
32
|
-
# if value
|
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
|
-
|
49
|
+
"Undefined"
|
41
50
|
end
|
42
51
|
|
52
|
+
# @api public
|
43
53
|
def undefined.inspect
|
44
|
-
|
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
|
-
#
|
52
|
-
#
|
53
|
-
#
|
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
|
57
|
-
if
|
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
|
-
|
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
|
-
#
|
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
|
@@ -47,8 +49,8 @@ module Dry
|
|
47
49
|
# @api private
|
48
50
|
def deprecation_message(name, msg)
|
49
51
|
<<-MSG
|
50
|
-
#{
|
51
|
-
#{
|
52
|
+
#{name} is deprecated and will be removed in the next major version
|
53
|
+
#{msg}
|
52
54
|
MSG
|
53
55
|
end
|
54
56
|
|
@@ -70,7 +72,7 @@ module Dry
|
|
70
72
|
# @param [IO] output output stream
|
71
73
|
#
|
72
74
|
# @return [Logger]
|
73
|
-
def logger(output =
|
75
|
+
def logger(output = $stderr)
|
74
76
|
if defined?(@logger)
|
75
77
|
@logger
|
76
78
|
else
|
@@ -95,7 +97,7 @@ module Dry
|
|
95
97
|
@logger = output
|
96
98
|
else
|
97
99
|
@logger = Logger.new(output).tap do |logger|
|
98
|
-
logger.formatter = proc { |_, _, _, msg| "#{
|
100
|
+
logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
|
99
101
|
end
|
100
102
|
end
|
101
103
|
end
|
@@ -153,7 +155,7 @@ module Dry
|
|
153
155
|
undef_method old_name if method_defined?(old_name)
|
154
156
|
|
155
157
|
define_method(old_name) do |*args, &block|
|
156
|
-
mod.warn("#{
|
158
|
+
mod.warn("#{full_msg}\n#{STACK.()}")
|
157
159
|
__send__(new_name, *args, &block)
|
158
160
|
end
|
159
161
|
else
|
@@ -163,7 +165,7 @@ module Dry
|
|
163
165
|
undef_method old_name
|
164
166
|
|
165
167
|
define_method(old_name) do |*args, &block|
|
166
|
-
mod.warn("#{
|
168
|
+
mod.warn("#{full_msg}\n#{STACK.()}")
|
167
169
|
__send__(aliased_name, *args, &block)
|
168
170
|
end
|
169
171
|
end
|
@@ -187,7 +189,7 @@ module Dry
|
|
187
189
|
undef_method old_name if method_defined?(old_name)
|
188
190
|
|
189
191
|
define_method(old_name) do |*args, &block|
|
190
|
-
warn("#{
|
192
|
+
warn("#{full_msg}\n#{STACK.()}")
|
191
193
|
meth.call(*args, &block)
|
192
194
|
end
|
193
195
|
end
|
@@ -208,7 +210,7 @@ module Dry
|
|
208
210
|
mod = Module.new do
|
209
211
|
define_method(:const_missing) do |missing|
|
210
212
|
if missing == constant_name
|
211
|
-
warn("#{
|
213
|
+
warn("#{full_msg}\n#{STACK.()}")
|
212
214
|
value
|
213
215
|
else
|
214
216
|
super(missing)
|
@@ -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
|