dry-initializer 3.0.4 → 3.1.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 +12 -0
- data/LICENSE +1 -1
- data/README.md +4 -3
- data/dry-initializer.gemspec +15 -13
- data/lib/dry/initializer/builders/attribute.rb +92 -82
- data/lib/dry/initializer/builders/initializer.rb +56 -54
- data/lib/dry/initializer/builders/reader.rb +55 -49
- data/lib/dry/initializer/builders/signature.rb +29 -23
- data/lib/dry/initializer/builders.rb +9 -5
- data/lib/dry/initializer/config.rb +160 -158
- data/lib/dry/initializer/definition.rb +58 -54
- data/lib/dry/initializer/dispatchers/build_nested_type.rb +54 -40
- data/lib/dry/initializer/dispatchers/check_type.rb +45 -39
- data/lib/dry/initializer/dispatchers/prepare_default.rb +32 -25
- data/lib/dry/initializer/dispatchers/prepare_ivar.rb +13 -6
- data/lib/dry/initializer/dispatchers/prepare_optional.rb +14 -7
- data/lib/dry/initializer/dispatchers/prepare_reader.rb +29 -22
- data/lib/dry/initializer/dispatchers/prepare_source.rb +12 -5
- data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -37
- data/lib/dry/initializer/dispatchers/unwrap_type.rb +21 -10
- data/lib/dry/initializer/dispatchers/wrap_type.rb +24 -17
- data/lib/dry/initializer/dispatchers.rb +48 -43
- data/lib/dry/initializer/dsl.rb +42 -34
- data/lib/dry/initializer/errors.rb +22 -0
- data/lib/dry/initializer/mixin/local.rb +19 -13
- data/lib/dry/initializer/mixin/root.rb +12 -7
- data/lib/dry/initializer/mixin.rb +17 -12
- data/lib/dry/initializer/struct.rb +34 -29
- data/lib/dry/initializer/undefined.rb +7 -1
- data/lib/dry/initializer/version.rb +3 -1
- data/lib/dry/initializer.rb +12 -9
- data/lib/dry-initializer.rb +3 -1
- data/lib/tasks/benchmark.rake +15 -13
- data/lib/tasks/profile.rake +20 -16
- metadata +5 -4
@@ -1,43 +1,49 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Checks whether an unwrapped type is valid
|
3
4
|
#
|
4
|
-
module Dry
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
5
|
+
module Dry
|
6
|
+
module Initializer
|
7
|
+
module Dispatchers
|
8
|
+
module CheckType
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def call(source:, type: nil, wrap: 0, **options)
|
12
|
+
check_if_callable! source, type
|
13
|
+
check_arity! source, type, wrap
|
14
|
+
|
15
|
+
{source: source, type: type, wrap: wrap, **options}
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check_if_callable!(source, type)
|
21
|
+
return if type.nil?
|
22
|
+
return if type.respond_to?(:call)
|
23
|
+
|
24
|
+
raise ArgumentError,
|
25
|
+
"The type of the argument '#{source}' should be callable"
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_arity!(_source, type, wrap)
|
29
|
+
return if type.nil?
|
30
|
+
return if wrap.zero?
|
31
|
+
return if type.method(:call).arity.abs == 1
|
32
|
+
|
33
|
+
raise ArgumentError, <<~MESSAGE
|
34
|
+
The dry_intitializer supports wrapped types with one argument only.
|
35
|
+
You cannot use array types with element coercers having several arguments.
|
36
|
+
|
37
|
+
For example, this definitions are correct:
|
38
|
+
option :foo, [proc(&:to_s)]
|
39
|
+
option :bar, type: [[]]
|
40
|
+
option :baz, ->(a, b) { [a, b] }
|
41
|
+
|
42
|
+
While this is not:
|
43
|
+
option :foo, [->(a, b) { [a, b] }]
|
44
|
+
MESSAGE
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
41
48
|
end
|
42
|
-
# rubocop: enable Metrics/MethodLength
|
43
49
|
end
|
@@ -1,40 +1,47 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Prepares the `:default` option
|
3
4
|
#
|
4
5
|
# It must respond to `.call` without arguments
|
5
6
|
#
|
6
|
-
module Dry
|
7
|
-
|
7
|
+
module Dry
|
8
|
+
module Initializer
|
9
|
+
module Dispatchers
|
10
|
+
module PrepareDefault
|
11
|
+
extend self
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
13
|
+
def call(default: nil, optional: nil, **options)
|
14
|
+
default = callable! default
|
15
|
+
check_arity! default
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
{default: default, optional: (optional | default), **options}
|
18
|
+
end
|
15
19
|
|
16
|
-
|
20
|
+
private
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
def callable!(default)
|
23
|
+
return unless default
|
24
|
+
return default if default.respond_to?(:call)
|
25
|
+
return callable(default.to_proc) if default.respond_to?(:to_proc)
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
invalid!(default)
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
def check_arity!(default)
|
31
|
+
return unless default
|
28
32
|
|
29
|
-
|
30
|
-
|
33
|
+
arity = default.method(:call).arity.to_i
|
34
|
+
return unless arity.positive?
|
31
35
|
|
32
|
-
|
33
|
-
|
36
|
+
invalid!(default)
|
37
|
+
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
def invalid!(default)
|
40
|
+
raise TypeError, "The #{default.inspect} should be" \
|
41
|
+
" either convertable to proc with no arguments," \
|
42
|
+
" or respond to #call without arguments."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
39
46
|
end
|
40
47
|
end
|
@@ -1,12 +1,19 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Prepares the variable name of a parameter or an option.
|
3
4
|
#
|
4
|
-
module Dry
|
5
|
-
|
5
|
+
module Dry
|
6
|
+
module Initializer
|
7
|
+
module Dispatchers
|
8
|
+
module PrepareIvar
|
9
|
+
module_function
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
def call(target:, **options)
|
12
|
+
ivar = "@#{target}".delete("?").to_sym
|
9
13
|
|
10
|
-
|
14
|
+
{target: target, ivar: ivar, **options}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
11
18
|
end
|
12
19
|
end
|
@@ -1,13 +1,20 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Defines whether an argument is optional
|
3
4
|
#
|
4
|
-
module Dry
|
5
|
-
|
5
|
+
module Dry
|
6
|
+
module Initializer
|
7
|
+
module Dispatchers
|
8
|
+
module PrepareOptional
|
9
|
+
module_function
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
def call(optional: nil, default: nil, required: nil, **options)
|
12
|
+
optional ||= default
|
13
|
+
optional &&= !required
|
10
14
|
|
11
|
-
|
15
|
+
{optional: !!optional, default: default, **options}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
12
19
|
end
|
13
20
|
end
|
@@ -1,30 +1,37 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Checks the reader privacy
|
3
4
|
#
|
4
|
-
module Dry
|
5
|
-
|
5
|
+
module Dry
|
6
|
+
module Initializer
|
7
|
+
module Dispatchers
|
8
|
+
module PrepareReader
|
9
|
+
extend self
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def call(target: nil, reader: :public, **options)
|
12
|
+
reader = case reader.to_s
|
13
|
+
when "false", "" then nil
|
14
|
+
when "true" then :public
|
15
|
+
when "public", "private", "protected" then reader.to_sym
|
16
|
+
else invalid_reader!(target, reader)
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
{target: target, reader: reader, **options}
|
20
|
+
end
|
17
21
|
|
18
|
-
|
22
|
+
private
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def invalid_reader!(target, _reader)
|
25
|
+
raise ArgumentError, <<~MESSAGE
|
26
|
+
Invalid setting for the ##{target} reader's privacy.
|
27
|
+
Use the one of the following values for the `:reader` option:
|
28
|
+
- 'public' (true) for the public reader (default)
|
29
|
+
- 'private' for the private reader
|
30
|
+
- 'protected' for the protected reader
|
31
|
+
- nil (false) if no reader should be defined
|
32
|
+
MESSAGE
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# The dispatcher verifies a correctness of the source name
|
3
4
|
# of param or option, taken as a `:source` option.
|
4
5
|
#
|
@@ -19,10 +20,16 @@
|
|
19
20
|
# foo.second # => 666
|
20
21
|
# ```
|
21
22
|
#
|
22
|
-
module Dry
|
23
|
-
|
23
|
+
module Dry
|
24
|
+
module Initializer
|
25
|
+
module Dispatchers
|
26
|
+
module PrepareSource
|
27
|
+
module_function
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
def call(source:, **options)
|
30
|
+
{source: source.to_s.to_sym, **options}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
@@ -1,44 +1,51 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Prepares the target name of a parameter or an option.
|
3
4
|
#
|
4
5
|
# Unlike source, the target must satisfy requirements for Ruby variable names.
|
5
6
|
# It also shouldn't be in conflict with names used by the gem.
|
6
7
|
#
|
7
|
-
module Dry
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
8
|
+
module Dry
|
9
|
+
module Initializer
|
10
|
+
module Dispatchers
|
11
|
+
module PrepareTarget
|
12
|
+
extend self
|
13
|
+
|
14
|
+
# List of variable names reserved by the gem
|
15
|
+
RESERVED = %i[
|
16
|
+
__dry_initializer_options__
|
17
|
+
__dry_initializer_config__
|
18
|
+
__dry_initializer_value__
|
19
|
+
__dry_initializer_definition__
|
20
|
+
__dry_initializer_initializer__
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def call(source:, target: nil, as: nil, **options)
|
24
|
+
target ||= as || source
|
25
|
+
target = target.to_s.to_sym.downcase
|
26
|
+
|
27
|
+
check_ruby_name!(target)
|
28
|
+
check_reserved_names!(target)
|
29
|
+
|
30
|
+
{source: source, target: target, **options}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def check_ruby_name!(target)
|
36
|
+
return if target[/\A[[:alpha:]_][[:alnum:]_]*\??\z/u]
|
37
|
+
|
38
|
+
raise ArgumentError,
|
39
|
+
"The name `#{target}` is not allowed for Ruby methods"
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_reserved_names!(target)
|
43
|
+
return unless RESERVED.include?(target)
|
44
|
+
|
45
|
+
raise ArgumentError,
|
46
|
+
"The method name `#{target}` is reserved by the dry-initializer gem"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
43
50
|
end
|
44
51
|
end
|
@@ -1,22 +1,33 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Looks at the `:type` option and counts how many nested arrays
|
3
4
|
# it contains around either nil or a callable value.
|
4
5
|
#
|
5
6
|
# The counted number is preserved in the `:wrap` virtual option
|
6
7
|
# used by the [WrapType] dispatcher.
|
7
8
|
#
|
8
|
-
module Dry
|
9
|
-
|
9
|
+
module Dry
|
10
|
+
module Initializer
|
11
|
+
module Dispatchers
|
12
|
+
module UnwrapType
|
13
|
+
extend self
|
10
14
|
|
11
|
-
|
12
|
-
|
15
|
+
def call(type: nil, wrap: 0, **options)
|
16
|
+
type, count = unwrap(type, wrap)
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
{type: type, wrap: count, **options}
|
19
|
+
end
|
16
20
|
|
17
|
-
|
21
|
+
private
|
18
22
|
|
19
|
-
|
20
|
-
|
23
|
+
def unwrap(type, count)
|
24
|
+
if type.is_a?(::Array)
|
25
|
+
unwrap(type.first, count + 1)
|
26
|
+
else
|
27
|
+
[type, count]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
21
32
|
end
|
22
33
|
end
|
@@ -1,28 +1,35 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# Takes `:type` and `:wrap` to construct the final value coercer
|
3
4
|
#
|
4
|
-
module Dry
|
5
|
-
|
5
|
+
module Dry
|
6
|
+
module Initializer
|
7
|
+
module Dispatchers
|
8
|
+
module WrapType
|
9
|
+
extend self
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
def call(type: nil, wrap: 0, **options)
|
12
|
+
{type: wrapped_type(type, wrap), **options}
|
13
|
+
end
|
10
14
|
|
11
|
-
|
15
|
+
private
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
def wrapped_type(type, count)
|
18
|
+
return type if count.zero?
|
15
19
|
|
16
|
-
|
17
|
-
|
20
|
+
->(value) { wrap_value(value, count, type) }
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def wrap_value(value, count, type)
|
24
|
+
if count.zero?
|
25
|
+
type ? type.call(value) : value
|
26
|
+
else
|
27
|
+
return [wrap_value(value, count - 1, type)] unless value.is_a?(Array)
|
24
28
|
|
25
|
-
|
29
|
+
value.map { |item| wrap_value(item, count - 1, type) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# The module is responsible for __normalizing__ arguments
|
3
4
|
# of `.param` and `.option`.
|
4
5
|
#
|
@@ -60,53 +61,57 @@
|
|
60
61
|
# param :id, integer: true
|
61
62
|
# end
|
62
63
|
#
|
63
|
-
module Dry
|
64
|
-
|
64
|
+
module Dry
|
65
|
+
module Initializer
|
66
|
+
module Dispatchers
|
67
|
+
extend self
|
65
68
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
+
# @!attribute [rw] null Defines a value to be set to unassigned attributes
|
70
|
+
# @return [Object]
|
71
|
+
attr_accessor :null
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
#
|
74
|
+
# Registers a new dispatcher
|
75
|
+
#
|
76
|
+
# @param [#call] dispatcher
|
77
|
+
# @return [self] itself
|
78
|
+
#
|
79
|
+
def <<(dispatcher)
|
80
|
+
@pipeline = [dispatcher] + pipeline
|
81
|
+
self
|
82
|
+
end
|
80
83
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
#
|
85
|
+
# Normalizes the source set of options
|
86
|
+
#
|
87
|
+
# @param [Hash<Symbol, Object>] options
|
88
|
+
# @return [Hash<Symbol, Objct>] normalized set of options
|
89
|
+
#
|
90
|
+
def call(**options)
|
91
|
+
options = {null: null, **options}
|
92
|
+
pipeline.reduce(options) { |opts, dispatcher| dispatcher.call(**opts) }
|
93
|
+
end
|
91
94
|
|
92
|
-
|
95
|
+
private
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
require_relative "dispatchers/build_nested_type"
|
98
|
+
require_relative "dispatchers/check_type"
|
99
|
+
require_relative "dispatchers/prepare_default"
|
100
|
+
require_relative "dispatchers/prepare_ivar"
|
101
|
+
require_relative "dispatchers/prepare_optional"
|
102
|
+
require_relative "dispatchers/prepare_reader"
|
103
|
+
require_relative "dispatchers/prepare_source"
|
104
|
+
require_relative "dispatchers/prepare_target"
|
105
|
+
require_relative "dispatchers/unwrap_type"
|
106
|
+
require_relative "dispatchers/wrap_type"
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
def pipeline
|
109
|
+
@pipeline ||= [
|
110
|
+
PrepareSource, PrepareTarget, PrepareIvar, PrepareReader,
|
111
|
+
PrepareDefault, PrepareOptional,
|
112
|
+
UnwrapType, CheckType, BuildNestedType, WrapType
|
113
|
+
]
|
114
|
+
end
|
115
|
+
end
|
111
116
|
end
|
112
117
|
end
|