dry-core 0.4.6 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|