dry-initializer 3.0.2
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 +7 -0
- data/.codeclimate.yml +12 -0
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/custom_ci.yml +74 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +34 -0
- data/.gitignore +12 -0
- data/.rspec +4 -0
- data/.rubocop.yml +89 -0
- data/CHANGELOG.md +890 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +38 -0
- data/Guardfile +5 -0
- data/LICENSE +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +89 -0
- data/Rakefile +8 -0
- data/benchmarks/compare_several_defaults.rb +82 -0
- data/benchmarks/plain_options.rb +63 -0
- data/benchmarks/plain_params.rb +84 -0
- data/benchmarks/with_coercion.rb +71 -0
- data/benchmarks/with_defaults.rb +66 -0
- data/benchmarks/with_defaults_and_coercion.rb +59 -0
- data/docsite/source/attributes.html.md +106 -0
- data/docsite/source/container-version.html.md +39 -0
- data/docsite/source/index.html.md +43 -0
- data/docsite/source/inheritance.html.md +43 -0
- data/docsite/source/optionals-and-defaults.html.md +130 -0
- data/docsite/source/options-tolerance.html.md +27 -0
- data/docsite/source/params-and-options.html.md +74 -0
- data/docsite/source/rails-support.html.md +101 -0
- data/docsite/source/readers.html.md +43 -0
- data/docsite/source/skip-undefined.html.md +59 -0
- data/docsite/source/type-constraints.html.md +160 -0
- data/dry-initializer.gemspec +20 -0
- data/lib/dry-initializer.rb +1 -0
- data/lib/dry/initializer.rb +61 -0
- data/lib/dry/initializer/builders.rb +7 -0
- data/lib/dry/initializer/builders/attribute.rb +81 -0
- data/lib/dry/initializer/builders/initializer.rb +61 -0
- data/lib/dry/initializer/builders/reader.rb +50 -0
- data/lib/dry/initializer/builders/signature.rb +32 -0
- data/lib/dry/initializer/config.rb +184 -0
- data/lib/dry/initializer/definition.rb +65 -0
- data/lib/dry/initializer/dispatchers.rb +112 -0
- data/lib/dry/initializer/dispatchers/build_nested_type.rb +59 -0
- data/lib/dry/initializer/dispatchers/check_type.rb +43 -0
- data/lib/dry/initializer/dispatchers/prepare_default.rb +40 -0
- data/lib/dry/initializer/dispatchers/prepare_ivar.rb +12 -0
- data/lib/dry/initializer/dispatchers/prepare_optional.rb +13 -0
- data/lib/dry/initializer/dispatchers/prepare_reader.rb +30 -0
- data/lib/dry/initializer/dispatchers/prepare_source.rb +28 -0
- data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -0
- data/lib/dry/initializer/dispatchers/unwrap_type.rb +22 -0
- data/lib/dry/initializer/dispatchers/wrap_type.rb +27 -0
- data/lib/dry/initializer/dsl.rb +43 -0
- data/lib/dry/initializer/mixin.rb +15 -0
- data/lib/dry/initializer/mixin/local.rb +19 -0
- data/lib/dry/initializer/mixin/root.rb +11 -0
- data/lib/dry/initializer/struct.rb +39 -0
- data/lib/dry/initializer/undefined.rb +2 -0
- data/lib/tasks/benchmark.rake +41 -0
- data/lib/tasks/profile.rake +78 -0
- data/spec/attributes_spec.rb +38 -0
- data/spec/coercion_of_nil_spec.rb +25 -0
- data/spec/custom_dispatchers_spec.rb +35 -0
- data/spec/custom_initializer_spec.rb +30 -0
- data/spec/default_values_spec.rb +83 -0
- data/spec/definition_spec.rb +111 -0
- data/spec/invalid_default_spec.rb +13 -0
- data/spec/list_type_spec.rb +32 -0
- data/spec/missed_default_spec.rb +14 -0
- data/spec/nested_type_spec.rb +48 -0
- data/spec/optional_spec.rb +71 -0
- data/spec/options_tolerance_spec.rb +11 -0
- data/spec/public_attributes_utility_spec.rb +22 -0
- data/spec/reader_spec.rb +87 -0
- data/spec/repetitive_definitions_spec.rb +69 -0
- data/spec/several_assignments_spec.rb +41 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/subclassing_spec.rb +49 -0
- data/spec/type_argument_spec.rb +35 -0
- data/spec/type_constraint_spec.rb +78 -0
- data/spec/value_coercion_via_dry_types_spec.rb +29 -0
- metadata +209 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "dry-initializer"
|
3
|
+
gem.version = "3.0.2"
|
4
|
+
gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
|
5
|
+
gem.email = "andrew.kozin@gmail.com"
|
6
|
+
gem.homepage = "https://github.com/dry-rb/dry-initializer"
|
7
|
+
gem.summary = "DSL for declaring params and options of the initializer"
|
8
|
+
gem.license = "MIT"
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
11
|
+
gem.test_files = gem.files.grep(/^spec/)
|
12
|
+
gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
|
13
|
+
|
14
|
+
gem.required_ruby_version = ">= 2.3"
|
15
|
+
|
16
|
+
gem.add_development_dependency "rspec", "~> 3.0"
|
17
|
+
gem.add_development_dependency "rake", "> 10"
|
18
|
+
gem.add_development_dependency "dry-types", "> 0.5.1"
|
19
|
+
gem.add_development_dependency "rubocop", "~> 0.49.0"
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "dry/initializer"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# Namespace for gems in a dry-rb community
|
4
|
+
module Dry
|
5
|
+
#
|
6
|
+
# DSL for declaring params and options of class initializers
|
7
|
+
#
|
8
|
+
module Initializer
|
9
|
+
require_relative "initializer/undefined"
|
10
|
+
require_relative "initializer/dsl"
|
11
|
+
require_relative "initializer/definition"
|
12
|
+
require_relative "initializer/builders"
|
13
|
+
require_relative "initializer/config"
|
14
|
+
require_relative "initializer/mixin"
|
15
|
+
require_relative "initializer/dispatchers"
|
16
|
+
|
17
|
+
# Adds methods [.[]] and [.define]
|
18
|
+
extend DSL
|
19
|
+
|
20
|
+
# Gem-related configuration
|
21
|
+
# @return [Dry::Initializer::Config]
|
22
|
+
def dry_initializer
|
23
|
+
@dry_initializer ||= Config.new(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds or redefines a parameter of [#dry_initializer]
|
27
|
+
# @param [Symbol] name
|
28
|
+
# @param [#call, nil] type (nil)
|
29
|
+
# @option opts [Proc] :default
|
30
|
+
# @option opts [Boolean] :optional
|
31
|
+
# @option opts [Symbol] :as
|
32
|
+
# @option opts [true, false, :protected, :public, :private] :reader
|
33
|
+
# @yield block with nested definition
|
34
|
+
# @return [self] itself
|
35
|
+
def param(name, type = nil, **opts, &block)
|
36
|
+
dry_initializer.param(name, type, **opts, &block)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds or redefines an option of [#dry_initializer]
|
41
|
+
# @param (see #param)
|
42
|
+
# @option (see #param)
|
43
|
+
# @yield (see #param)
|
44
|
+
# @return (see #param)
|
45
|
+
def option(name, type = nil, **opts, &block)
|
46
|
+
dry_initializer.option(name, type, **opts, &block)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def inherited(klass)
|
53
|
+
super
|
54
|
+
config = Config.new(klass, null: dry_initializer.null)
|
55
|
+
klass.send(:instance_variable_set, :@dry_initializer, config)
|
56
|
+
dry_initializer.children << config
|
57
|
+
end
|
58
|
+
|
59
|
+
require_relative "initializer/struct"
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Dry::Initializer::Builders
|
2
|
+
# @private
|
3
|
+
class Attribute
|
4
|
+
def self.[](definition)
|
5
|
+
new(definition).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
lines.compact
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# rubocop: disable Metrics/MethodLength
|
15
|
+
def initialize(definition)
|
16
|
+
@definition = definition
|
17
|
+
@option = definition.option
|
18
|
+
@type = definition.type
|
19
|
+
@optional = definition.optional
|
20
|
+
@default = definition.default
|
21
|
+
@source = definition.source
|
22
|
+
@ivar = definition.ivar
|
23
|
+
@null = definition.null ? "Dry::Initializer::UNDEFINED" : "nil"
|
24
|
+
@opts = "__dry_initializer_options__"
|
25
|
+
@congif = "__dry_initializer_config__"
|
26
|
+
@item = "__dry_initializer_definition__"
|
27
|
+
@val = @option ? "__dry_initializer_value__" : @source
|
28
|
+
end
|
29
|
+
# rubocop: enable Metrics/MethodLength
|
30
|
+
|
31
|
+
def lines
|
32
|
+
[
|
33
|
+
"",
|
34
|
+
definition_line,
|
35
|
+
reader_line,
|
36
|
+
default_line,
|
37
|
+
coercion_line,
|
38
|
+
assignment_line
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def reader_line
|
43
|
+
return unless @option
|
44
|
+
@optional ? optional_reader : required_reader
|
45
|
+
end
|
46
|
+
|
47
|
+
def optional_reader
|
48
|
+
"#{@val} = #{@opts}.fetch(:'#{@source}', #{@null})"
|
49
|
+
end
|
50
|
+
|
51
|
+
def required_reader
|
52
|
+
"#{@val} = #{@opts}.fetch(:'#{@source}')" \
|
53
|
+
" { raise KeyError, \"\#{self.class}: #{@definition} is required\" }"
|
54
|
+
end
|
55
|
+
|
56
|
+
def definition_line
|
57
|
+
return unless @type || @default
|
58
|
+
"#{@item} = __dry_initializer_config__.definitions[:'#{@source}']"
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_line
|
62
|
+
return unless @default
|
63
|
+
"#{@val} = instance_exec(&#{@item}.default) if #{@null} == #{@val}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def coercion_line
|
67
|
+
return unless @type
|
68
|
+
arity = @type.is_a?(Proc) ? @type.arity : @type.method(:call).arity
|
69
|
+
if arity.abs == 1
|
70
|
+
"#{@val} = #{@item}.type.call(#{@val}) unless #{@null} == #{@val}"
|
71
|
+
else
|
72
|
+
"#{@val} = #{@item}.type.call(#{@val}, self) unless #{@null} == #{@val}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def assignment_line
|
77
|
+
"#{@ivar} = #{@val}" \
|
78
|
+
" unless #{@null} == #{@val} && instance_variable_defined?(:#{@ivar})"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Dry::Initializer::Builders
|
2
|
+
# @private
|
3
|
+
class Initializer
|
4
|
+
require_relative "signature"
|
5
|
+
require_relative "attribute"
|
6
|
+
|
7
|
+
def self.[](config)
|
8
|
+
new(config).call
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
lines.flatten.compact.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def initialize(config)
|
18
|
+
@config = config
|
19
|
+
@definitions = config.definitions.values
|
20
|
+
end
|
21
|
+
|
22
|
+
def lines
|
23
|
+
[
|
24
|
+
undef_line,
|
25
|
+
define_line,
|
26
|
+
params_lines,
|
27
|
+
options_lines,
|
28
|
+
end_line
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
def undef_line
|
33
|
+
"undef :__dry_initializer_initialize__" \
|
34
|
+
" if private_method_defined? :__dry_initializer_initialize__"
|
35
|
+
end
|
36
|
+
|
37
|
+
def define_line
|
38
|
+
"private def __dry_initializer_initialize__(#{Signature[@config]})"
|
39
|
+
end
|
40
|
+
|
41
|
+
def params_lines
|
42
|
+
@definitions.reject(&:option)
|
43
|
+
.flat_map { |item| Attribute[item] }
|
44
|
+
.map { |line| " " << line }
|
45
|
+
end
|
46
|
+
|
47
|
+
def options_lines
|
48
|
+
@definitions.select(&:option)
|
49
|
+
.flat_map { |item| Attribute[item] }
|
50
|
+
.map { |line| " " << line }
|
51
|
+
end
|
52
|
+
|
53
|
+
def end_line
|
54
|
+
"end"
|
55
|
+
end
|
56
|
+
|
57
|
+
def private_line
|
58
|
+
"private :__dry_initializer_initialize__"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Dry::Initializer::Builders
|
2
|
+
# @private
|
3
|
+
class Reader
|
4
|
+
def self.[](definition)
|
5
|
+
new(definition).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
lines.flatten.compact.join("\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize(definition)
|
15
|
+
@target = definition.target
|
16
|
+
@ivar = definition.ivar
|
17
|
+
@null = definition.null
|
18
|
+
@reader = definition.reader
|
19
|
+
end
|
20
|
+
|
21
|
+
def lines
|
22
|
+
[undef_line, attribute_line, method_lines, type_line]
|
23
|
+
end
|
24
|
+
|
25
|
+
def undef_line
|
26
|
+
"undef :#{@target} if method_defined?(:#{@target})" \
|
27
|
+
" || private_method_defined?(:#{@target})" \
|
28
|
+
" || protected_method_defined?(:#{@target})"
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_line
|
32
|
+
return unless @reader
|
33
|
+
"attr_reader :#{@target}" unless @null
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_lines
|
37
|
+
return unless @reader
|
38
|
+
return unless @null
|
39
|
+
[
|
40
|
+
"def #{@target}",
|
41
|
+
" #{@ivar} unless Dry::Initializer::UNDEFINED == #{@ivar}",
|
42
|
+
"end"
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
def type_line
|
47
|
+
"#{@reader} :#{@target}" if %i[private protected].include? @reader
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Dry::Initializer::Builders
|
2
|
+
# @private
|
3
|
+
class Signature
|
4
|
+
def self.[](config)
|
5
|
+
new(config).call
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
[*required_params, *optional_params, "*", options].compact.join(", ")
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
16
|
+
@options = config.options.any?
|
17
|
+
@null = config.null ? "Dry::Initializer::UNDEFINED" : "nil"
|
18
|
+
end
|
19
|
+
|
20
|
+
def required_params
|
21
|
+
@config.params.reject(&:optional).map(&:source)
|
22
|
+
end
|
23
|
+
|
24
|
+
def optional_params
|
25
|
+
@config.params.select(&:optional).map { |rec| "#{rec.source} = #{@null}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
def options
|
29
|
+
"**__dry_initializer_options__" if @options
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
#
|
3
|
+
# Gem-related configuration of some class
|
4
|
+
#
|
5
|
+
class Config
|
6
|
+
# @!attribute [r] null
|
7
|
+
# @return [Dry::Initializer::UNDEFINED, nil] value of unassigned variable
|
8
|
+
|
9
|
+
# @!attribute [r] extended_class
|
10
|
+
# @return [Class] the class whose config collected by current object
|
11
|
+
|
12
|
+
# @!attribute [r] parent
|
13
|
+
# @return [Dry::Initializer::Config] parent configuration
|
14
|
+
|
15
|
+
# @!attribute [r] definitions
|
16
|
+
# @return [Hash<Symbol, Dry::Initializer::Definition>]
|
17
|
+
# hash of attribute definitions with their source names
|
18
|
+
|
19
|
+
attr_reader :null, :extended_class, :parent, :definitions
|
20
|
+
|
21
|
+
# @!attribute [r] mixin
|
22
|
+
# @return [Module] reference to the module to be included into class
|
23
|
+
def mixin
|
24
|
+
@mixin ||= Module.new.tap do |mod|
|
25
|
+
__dry_initializer__ = self
|
26
|
+
mod.extend(Mixin::Local)
|
27
|
+
mod.send :define_method, :__dry_initializer_config__ do
|
28
|
+
__dry_initializer__
|
29
|
+
end
|
30
|
+
mod.send :private, :__dry_initializer_config__
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# List of configs of all subclasses of the [#extended_class]
|
35
|
+
# @return [Array<Dry::Initializer::Config>]
|
36
|
+
def children
|
37
|
+
@children ||= Set.new
|
38
|
+
end
|
39
|
+
|
40
|
+
# List of definitions for initializer params
|
41
|
+
# @return [Array<Dry::Initializer::Definition>]
|
42
|
+
def params
|
43
|
+
definitions.values.reject(&:option)
|
44
|
+
end
|
45
|
+
|
46
|
+
# List of definitions for initializer options
|
47
|
+
# @return [Array<Dry::Initializer::Definition>]
|
48
|
+
def options
|
49
|
+
definitions.values.select(&:option)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds or redefines a parameter
|
53
|
+
# @param [Symbol] name
|
54
|
+
# @param [#call, nil] type (nil)
|
55
|
+
# @option opts [Proc] :default
|
56
|
+
# @option opts [Boolean] :optional
|
57
|
+
# @option opts [Symbol] :as
|
58
|
+
# @option opts [true, false, :protected, :public, :private] :reader
|
59
|
+
# @return [self] itself
|
60
|
+
def param(name, type = nil, **opts, &block)
|
61
|
+
add_definition(false, name, type, block, **opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds or redefines an option of [#dry_initializer]
|
65
|
+
#
|
66
|
+
# @param (see #param)
|
67
|
+
# @option (see #param)
|
68
|
+
# @return (see #param)
|
69
|
+
#
|
70
|
+
def option(name, type = nil, **opts, &block)
|
71
|
+
add_definition(true, name, type, block, **opts)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The hash of public attributes for an instance of the [#extended_class]
|
75
|
+
# @param [Dry::Initializer::Instance] instance
|
76
|
+
# @return [Hash<Symbol, Object>]
|
77
|
+
def public_attributes(instance)
|
78
|
+
definitions.values.each_with_object({}) do |item, obj|
|
79
|
+
key = item.target
|
80
|
+
next unless instance.respond_to? key
|
81
|
+
val = instance.send(key)
|
82
|
+
obj[key] = val unless null == val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# The hash of assigned attributes for an instance of the [#extended_class]
|
87
|
+
# @param [Dry::Initializer::Instance] instance
|
88
|
+
# @return [Hash<Symbol, Object>]
|
89
|
+
def attributes(instance)
|
90
|
+
definitions.values.each_with_object({}) do |item, obj|
|
91
|
+
key = item.target
|
92
|
+
val = instance.send(:instance_variable_get, item.ivar)
|
93
|
+
obj[key] = val unless null == val
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Code of the `#__initialize__` method
|
98
|
+
# @return [String]
|
99
|
+
def code
|
100
|
+
Builders::Initializer[self]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Finalizes config
|
104
|
+
# @return [self]
|
105
|
+
def finalize
|
106
|
+
@definitions = final_definitions
|
107
|
+
check_order_of_params
|
108
|
+
mixin.class_eval(code)
|
109
|
+
children.each(&:finalize)
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
# Human-readable representation of configured params and options
|
114
|
+
# @return [String]
|
115
|
+
def inch
|
116
|
+
line = Builders::Signature[self]
|
117
|
+
line = line.gsub("__dry_initializer_options__", "options")
|
118
|
+
lines = ["@!method initialize(#{line})"]
|
119
|
+
lines += ["Initializes an instance of #{extended_class}"]
|
120
|
+
lines += definitions.values.map(&:inch)
|
121
|
+
lines += ["@return [#{extended_class}]"]
|
122
|
+
lines.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def initialize(extended_class = nil, null: UNDEFINED)
|
128
|
+
@extended_class = extended_class.tap { |klass| klass&.include mixin }
|
129
|
+
sklass = extended_class&.superclass
|
130
|
+
@parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
|
131
|
+
@null = null || parent&.null
|
132
|
+
@definitions = {}
|
133
|
+
finalize
|
134
|
+
end
|
135
|
+
|
136
|
+
# rubocop: disable Metrics/MethodLength
|
137
|
+
def add_definition(option, name, type, block, **opts)
|
138
|
+
opts = {
|
139
|
+
parent: extended_class,
|
140
|
+
option: option,
|
141
|
+
null: null,
|
142
|
+
source: name,
|
143
|
+
type: type,
|
144
|
+
block: block,
|
145
|
+
**opts
|
146
|
+
}
|
147
|
+
|
148
|
+
options = Dispatchers.call(**opts)
|
149
|
+
definition = Definition.new(**options)
|
150
|
+
definitions[definition.source] = definition
|
151
|
+
finalize
|
152
|
+
mixin.class_eval definition.code
|
153
|
+
end
|
154
|
+
# rubocop: enable Metrics/MethodLength
|
155
|
+
|
156
|
+
def final_definitions
|
157
|
+
parent_definitions = Hash(parent&.definitions&.dup)
|
158
|
+
definitions.each_with_object(parent_definitions) do |(key, val), obj|
|
159
|
+
obj[key] = check_type(obj[key], val)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def check_type(previous, current)
|
164
|
+
return current unless previous
|
165
|
+
return current if previous.option == current.option
|
166
|
+
raise SyntaxError,
|
167
|
+
"cannot reload #{previous} of #{extended_class.superclass}" \
|
168
|
+
" by #{current} of its subclass #{extended_class}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def check_order_of_params
|
172
|
+
params.inject(nil) do |optional, current|
|
173
|
+
if current.optional
|
174
|
+
current
|
175
|
+
elsif optional
|
176
|
+
raise SyntaxError, "#{extended_class}: required #{current}" \
|
177
|
+
" goes after optional #{optional}"
|
178
|
+
else
|
179
|
+
optional
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|