dry-initializer 0.11.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +1 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +14 -62
  6. data/.travis.yml +15 -11
  7. data/CHANGELOG.md +538 -158
  8. data/Gemfile +2 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +6 -6
  11. data/Rakefile +2 -41
  12. data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
  13. data/benchmarks/{without_options.rb → plain_options.rb} +21 -10
  14. data/benchmarks/{params.rb → plain_params.rb} +20 -9
  15. data/benchmarks/{with_types.rb → with_coercion.rb} +23 -16
  16. data/benchmarks/with_defaults.rb +19 -8
  17. data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -12
  18. data/dry-initializer.gemspec +4 -4
  19. data/lib/dry/initializer/builders/attribute.rb +81 -0
  20. data/lib/dry/initializer/builders/initializer.rb +61 -0
  21. data/lib/dry/initializer/builders/reader.rb +50 -0
  22. data/lib/dry/initializer/builders/signature.rb +32 -0
  23. data/lib/dry/initializer/builders.rb +7 -0
  24. data/lib/dry/initializer/config.rb +172 -0
  25. data/lib/dry/initializer/definition.rb +116 -0
  26. data/lib/dry/initializer/dispatchers.rb +44 -0
  27. data/lib/dry/initializer/dsl.rb +43 -0
  28. data/lib/dry/initializer/mixin/local.rb +19 -0
  29. data/lib/dry/initializer/mixin/root.rb +10 -0
  30. data/lib/dry/initializer/mixin.rb +8 -70
  31. data/lib/dry/initializer.rb +49 -12
  32. data/lib/tasks/benchmark.rake +41 -0
  33. data/lib/tasks/profile.rake +78 -0
  34. data/spec/attributes_spec.rb +38 -0
  35. data/spec/coercion_of_nil_spec.rb +25 -0
  36. data/spec/custom_dispatchers_spec.rb +35 -0
  37. data/spec/custom_initializer_spec.rb +1 -1
  38. data/spec/default_values_spec.rb +8 -8
  39. data/spec/definition_spec.rb +107 -0
  40. data/spec/invalid_default_spec.rb +2 -2
  41. data/spec/missed_default_spec.rb +3 -3
  42. data/spec/optional_spec.rb +35 -8
  43. data/spec/options_tolerance_spec.rb +1 -1
  44. data/spec/public_attributes_utility_spec.rb +22 -0
  45. data/spec/reader_spec.rb +11 -11
  46. data/spec/repetitive_definitions_spec.rb +41 -21
  47. data/spec/several_assignments_spec.rb +41 -0
  48. data/spec/spec_helper.rb +7 -0
  49. data/spec/subclassing_spec.rb +7 -3
  50. data/spec/type_argument_spec.rb +1 -1
  51. data/spec/type_constraint_spec.rb +38 -7
  52. data/spec/value_coercion_via_dry_types_spec.rb +12 -4
  53. metadata +37 -40
  54. data/benchmarks/options.rb +0 -54
  55. data/benchmarks/params_vs_options.rb +0 -35
  56. data/lib/dry/initializer/builder.rb +0 -100
  57. data/lib/dry/initializer/errors/default_value_error.rb +0 -6
  58. data/lib/dry/initializer/errors/order_error.rb +0 -7
  59. data/lib/dry/initializer/errors/plugin_error.rb +0 -6
  60. data/lib/dry/initializer/errors/redefinition_error.rb +0 -5
  61. data/lib/dry/initializer/errors/type_constraint_error.rb +0 -5
  62. data/lib/dry/initializer/errors.rb +0 -10
  63. data/lib/dry/initializer/plugins/base.rb +0 -47
  64. data/lib/dry/initializer/plugins/default_proc.rb +0 -28
  65. data/lib/dry/initializer/plugins/signature.rb +0 -28
  66. data/lib/dry/initializer/plugins/type_constraint.rb +0 -21
  67. data/lib/dry/initializer/plugins/variable_setter.rb +0 -30
  68. data/lib/dry/initializer/plugins.rb +0 -10
  69. data/lib/dry/initializer/signature.rb +0 -61
  70. data/spec/base_spec.rb +0 -21
  71. data/spec/container_spec.rb +0 -45
  72. data/spec/default_nil_spec.rb +0 -17
  73. data/spec/plugin_registry_spec.rb +0 -45
  74. data/spec/renaming_options_spec.rb +0 -20
@@ -0,0 +1,172 @@
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)
61
+ add_definition(false, name, type, 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)
71
+ add_definition(true, name, type, 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
+ def add_definition(option, name, type, opts)
137
+ definition = Definition.new(option, null, name, type, Dispatchers[opts])
138
+ definitions[definition.source] = definition
139
+ finalize
140
+
141
+ mixin.class_eval definition.code
142
+ end
143
+
144
+ def final_definitions
145
+ parent_definitions = Hash(parent&.definitions&.dup)
146
+ definitions.each_with_object(parent_definitions) do |(key, val), obj|
147
+ obj[key] = check_type(obj[key], val)
148
+ end
149
+ end
150
+
151
+ def check_type(previous, current)
152
+ return current unless previous
153
+ return current if previous.option == current.option
154
+ raise SyntaxError,
155
+ "cannot reload #{previous} of #{extended_class.superclass}" \
156
+ " by #{current} of its subclass #{extended_class}"
157
+ end
158
+
159
+ def check_order_of_params
160
+ params.inject(nil) do |optional, current|
161
+ if current.optional
162
+ current
163
+ elsif optional
164
+ raise SyntaxError, "#{extended_class}: required #{current}" \
165
+ " goes after optional #{optional}"
166
+ else
167
+ optional
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,116 @@
1
+ module Dry::Initializer
2
+ #
3
+ # @private
4
+ # @abstract
5
+ #
6
+ # Base class for parameter or option definitions
7
+ # Defines methods to add corresponding reader to the class,
8
+ # and build value of instance attribute.
9
+ #
10
+ class Definition
11
+ attr_reader :option, :null, :source, :target, :ivar,
12
+ :type, :optional, :default, :reader,
13
+ :desc
14
+
15
+ def options
16
+ {
17
+ as: target,
18
+ type: type,
19
+ optional: optional,
20
+ default: default,
21
+ reader: reader,
22
+ desc: desc
23
+ }.reject { |_, value| value.nil? }
24
+ end
25
+
26
+ def name
27
+ @name ||= (option ? "option" : "parameter") << " '#{source}'"
28
+ end
29
+ alias to_s name
30
+ alias to_str name
31
+ alias inspect name
32
+
33
+ def ==(other)
34
+ other.instance_of?(self.class) && (other.source == source)
35
+ end
36
+
37
+ def code
38
+ Builders::Reader[self]
39
+ end
40
+
41
+ def inch
42
+ @inch ||= (option ? "@option" : "@param ").tap do |text|
43
+ text << " [Object]"
44
+ text << (option ? " :#{source}" : " #{source}")
45
+ text << (optional ? " (optional)" : " (required)")
46
+ text << " #{desc}" if desc
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def initialize(option, null, source, coercer = nil, **options)
53
+ @option = !!option
54
+ @null = null
55
+ @source = source.to_sym
56
+ @target = check_target options.fetch(:as, source).to_sym
57
+ @ivar = :"@#{target}"
58
+ @type = check_type(coercer || options[:type])
59
+ @reader = prepare_reader options.fetch(:reader, true)
60
+ @default = check_default options[:default]
61
+ @optional = options.fetch(:optional, @default)
62
+ @desc = options[:desc]&.to_s&.capitalize
63
+ end
64
+
65
+ def check_source(value)
66
+ if RESERVED.include? value
67
+ raise ArgumentError, "Name #{value} is reserved by dry-initializer gem"
68
+ end
69
+
70
+ unless option || value[ATTRIBUTE]
71
+ raise ArgumentError, "Invalid parameter name :'#{value}'"
72
+ end
73
+
74
+ value
75
+ end
76
+
77
+ def check_target(value)
78
+ return value if value[ATTRIBUTE]
79
+ raise ArgumentError, "Invalid variable name :'#{value}'"
80
+ end
81
+
82
+ def check_type(value)
83
+ return if value.nil?
84
+ arity = value.arity if value.is_a? Proc
85
+ arity ||= value.method(:call).arity if value.respond_to? :call
86
+ return value if [1, 2].include? arity.to_i.abs
87
+ raise TypeError,
88
+ "type of #{inspect} should respond to #call with 1..2 arguments"
89
+ end
90
+
91
+ def check_default(value)
92
+ return if value.nil?
93
+ return value if value.is_a?(Proc) && value.arity < 1
94
+ raise TypeError,
95
+ "default value of #{inspect} should be a proc without params"
96
+ end
97
+
98
+ def prepare_reader(value)
99
+ case value.to_s
100
+ when "", "false" then false
101
+ when "private" then :private
102
+ when "protected" then :protected
103
+ else :public
104
+ end
105
+ end
106
+
107
+ ATTRIBUTE = /\A\w+\z/
108
+ RESERVED = %i[
109
+ __dry_initializer_options__
110
+ __dry_initializer_config__
111
+ __dry_initializer_value__
112
+ __dry_initializer_definition__
113
+ __dry_initializer_initializer__
114
+ ].freeze
115
+ end
116
+ end
@@ -0,0 +1,44 @@
1
+ module Dry::Initializer
2
+ #
3
+ # @private
4
+ #
5
+ # Dispatchers allow adding syntax sugar to `.param` and `.option` methods.
6
+ #
7
+ # Every dispatcher should convert the source hash of options into
8
+ # the resulting hash so that you can send additional keys to the helpers.
9
+ #
10
+ # @example Add special dispatcher
11
+ #
12
+ # # Define a dispatcher for key :integer
13
+ # dispatcher = proc do |opts|
14
+ # opts.merge(type: proc(&:to_i)) if opts[:integer]
15
+ # end
16
+ #
17
+ # # Register a dispatcher
18
+ # Dry::Initializer::Dispatchers << dispatcher
19
+ #
20
+ # # Now you can use option `integer: true` instead of `type: proc(&:to_i)`
21
+ # class Foo
22
+ # extend Dry::Initializer
23
+ # param :id, integer: true
24
+ # end
25
+ #
26
+ module Dispatchers
27
+ class << self
28
+ def <<(item)
29
+ list << item
30
+ self
31
+ end
32
+
33
+ def [](options)
34
+ list.inject(options) { |opts, item| item.call(opts) }
35
+ end
36
+
37
+ private
38
+
39
+ def list
40
+ @list ||= []
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ module Dry::Initializer
2
+ # Module-level DSL
3
+ module DSL
4
+ # Setting for null (undefined value)
5
+ # @return [nil, Dry::Initializer::UNDEFINED]
6
+ attr_reader :null
7
+
8
+ # Returns a version of the module with custom settings
9
+ # @option settings [Boolean] :undefined
10
+ # If unassigned params and options should be treated different from nil
11
+ # @return [Dry::Initializer]
12
+ def [](undefined: true, **)
13
+ null = (undefined == false) ? nil : UNDEFINED
14
+ Module.new.tap do |mod|
15
+ mod.extend DSL
16
+ mod.include self
17
+ mod.send(:instance_variable_set, :@null, null)
18
+ end
19
+ end
20
+
21
+ # Returns mixin module to be included to target class by hand
22
+ # @return [Module]
23
+ # @yield proc defining params and options
24
+ def define(procedure = nil, &block)
25
+ config = Config.new(null: null)
26
+ config.instance_exec(&(procedure || block))
27
+ config.mixin.include Mixin::Root
28
+ config.mixin
29
+ end
30
+
31
+ private
32
+
33
+ def extended(klass)
34
+ config = Config.new(klass, null: null)
35
+ klass.send :instance_variable_set, :@dry_initializer, config
36
+ klass.include Mixin::Root
37
+ end
38
+
39
+ def self.extended(mod)
40
+ mod.instance_variable_set :@null, UNDEFINED
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Dry::Initializer::Mixin
2
+ # @private
3
+ module Local
4
+ attr_reader :klass
5
+
6
+ def inspect
7
+ "Dry::Initializer::Mixin::Local[#{klass}]"
8
+ end
9
+ alias to_s inspect
10
+ alias to_str inspect
11
+
12
+ private
13
+
14
+ def included(klass)
15
+ @klass = klass
16
+ super
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Dry::Initializer::Mixin
2
+ # @private
3
+ module Root
4
+ private
5
+
6
+ def initialize(*args)
7
+ __dry_initializer_initialize__(*args)
8
+ end
9
+ end
10
+ end
@@ -1,77 +1,15 @@
1
1
  module Dry::Initializer
2
- # Class-level DSL for the initializer
2
+ # @private
3
3
  module Mixin
4
- # Declares a plain argument
5
- #
6
- # @param [#to_sym] name
7
- #
8
- # @option options [Object] :default The default value
9
- # @option options [#call] :type The type constraings via `dry-types`
10
- # @option options [Boolean] :reader (true) Whether to define attr_reader
11
- #
12
- # @return [self] itself
13
- #
14
- def param(name, type = nil, **options)
15
- options[:type] = type if type
16
- options[:option] = false
17
- @__initializer_builder__ = __initializer_builder__.define(name, **options)
18
- __initializer_builder__.call(__initializer_mixin__)
19
- end
20
-
21
- # Declares a named argument
22
- #
23
- # @param (see #param)
24
- # @option (see #param)
25
- # @return (see #param)
26
- #
27
- def option(name, type = nil, **options)
28
- options[:type] = type if type
29
- options[:option] = true
30
- @__initializer_builder__ = __initializer_builder__.define(name, **options)
31
- __initializer_builder__.call(__initializer_mixin__)
32
- end
33
-
34
- # Adds new plugin to the builder
35
- #
36
- # @param [Dry::Initializer::Plugins::Base] plugin
37
- # @return [self] itself
38
- #
39
- def register_initializer_plugin(plugin)
40
- @__initializer_builder__ = __initializer_builder__.register(plugin)
41
- __initializer_builder__.call(__initializer_mixin__)
42
- end
43
-
44
- private
45
-
46
- def __initializer_mixin__
47
- @__initializer_mixin__ ||= Module.new do
48
- def initialize(*args)
49
- __initialize__(*args)
50
- end
51
- end
52
- end
53
-
54
- def __initializer_builder__
55
- @__initializer_builder__ ||= Builder.new
56
- end
57
-
58
- def inherited(klass)
59
- new_builder = __initializer_builder__.dup
60
- klass.instance_variable_set :@__initializer_builder__, new_builder
61
-
62
- new_mixin = Module.new
63
- new_builder.call(new_mixin)
64
- klass.instance_variable_set :@__initializer_mixin__, new_mixin
65
- klass.include new_mixin
66
-
4
+ extend DSL # @deprecated
5
+ include Dry::Initializer # @deprecated
6
+ def self.extended(klass) # @deprecated
7
+ warn "[DEPRECATED] Use Dry::Initializer instead of its alias" \
8
+ " Dry::Initializer::Mixin. The later will be removed in v2.1.0"
67
9
  super
68
10
  end
69
11
 
70
- def self.extended(klass)
71
- super
72
- mixin = klass.send(:__initializer_mixin__)
73
- klass.send(:__initializer_builder__).call(mixin)
74
- klass.include mixin
75
- end
12
+ require_relative "mixin/root"
13
+ require_relative "mixin/local"
76
14
  end
77
15
  end
@@ -1,22 +1,59 @@
1
+ require "set"
2
+
3
+ # Namespace for gems in a dry-rb community
1
4
  module Dry
2
- # Declares arguments of the initializer (params and options)
3
5
  #
4
- # @api public
6
+ # DSL for declaring params and options of class initializers
5
7
  #
6
8
  module Initializer
7
- require_relative "initializer/errors"
8
- require_relative "initializer/plugins"
9
- require_relative "initializer/signature"
10
- require_relative "initializer/builder"
9
+ # Singleton for unassigned values
10
+ UNDEFINED = Object.new.freeze
11
+
12
+ require_relative "initializer/dsl"
13
+ require_relative "initializer/definition"
14
+ require_relative "initializer/builders"
15
+ require_relative "initializer/config"
11
16
  require_relative "initializer/mixin"
17
+ require_relative "initializer/dispatchers"
12
18
 
13
- UNDEFINED = Object.new.freeze
19
+ # Adds methods [.[]] and [.define]
20
+ extend DSL
21
+
22
+ # Gem-related configuration
23
+ # @return [Dry::Initializer::Config]
24
+ def dry_initializer
25
+ @dry_initializer ||= Config.new(self)
26
+ end
27
+
28
+ # Adds or redefines a parameter of [#dry_initializer]
29
+ # @param [Symbol] name
30
+ # @param [#call, nil] type (nil)
31
+ # @option opts [Proc] :default
32
+ # @option opts [Boolean] :optional
33
+ # @option opts [Symbol] :as
34
+ # @option opts [true, false, :protected, :public, :private] :reader
35
+ # @return [self] itself
36
+ def param(name, type = nil, **opts)
37
+ dry_initializer.param(name, type, Dispatchers[opts])
38
+ self
39
+ end
40
+
41
+ # Adds or redefines an option of [#dry_initializer]
42
+ # @param (see #param)
43
+ # @option (see #param)
44
+ # @return (see #param)
45
+ def option(name, type = nil, **opts)
46
+ dry_initializer.option(name, type, Dispatchers[opts])
47
+ self
48
+ end
49
+
50
+ private
14
51
 
15
- def self.define(proc = nil, &block)
16
- Module.new do |container|
17
- container.extend Mixin
18
- container.instance_exec(&(proc || block))
19
- end
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
20
57
  end
21
58
  end
22
59
  end
@@ -0,0 +1,41 @@
1
+ namespace :benchmark do
2
+ desc "Runs benchmarks for plain params"
3
+ task :plain_params do
4
+ system "ruby benchmarks/plain_params.rb"
5
+ end
6
+
7
+ desc "Runs benchmarks for plain options"
8
+ task :plain_options do
9
+ system "ruby benchmarks/plain_options.rb"
10
+ end
11
+
12
+ desc "Runs benchmarks for value coercion"
13
+ task :with_coercion do
14
+ system "ruby benchmarks/with_coercion.rb"
15
+ end
16
+
17
+ desc "Runs benchmarks with defaults"
18
+ task :with_defaults do
19
+ system "ruby benchmarks/with_defaults.rb"
20
+ end
21
+
22
+ desc "Runs benchmarks with defaults and coercion"
23
+ task :with_defaults_and_coercion do
24
+ system "ruby benchmarks/with_defaults_and_coercion.rb"
25
+ end
26
+
27
+ desc "Runs benchmarks for several defaults"
28
+ task :compare_several_defaults do
29
+ system "ruby benchmarks/with_several_defaults.rb"
30
+ end
31
+ end
32
+
33
+ desc "Runs all benchmarks"
34
+ task benchmark: %i[
35
+ benchmark:plain_params
36
+ benchmark:plain_options
37
+ benchmark:with_coercion
38
+ benchmark:with_defaults
39
+ benchmark:with_defaults_and_coercion
40
+ benchmark:compare_several_defaults
41
+ ]
@@ -0,0 +1,78 @@
1
+ namespace :profile do
2
+ def profile(name, execution, &definition)
3
+ require "dry-initializer"
4
+ require "ruby-prof"
5
+ require "fileutils"
6
+
7
+ definition.call
8
+ result = RubyProf.profile do
9
+ 1_000.times { execution.call }
10
+ end
11
+
12
+ FileUtils.mkdir_p "./tmp"
13
+
14
+ FileUtils.touch "./tmp/#{name}.dot"
15
+ File.open("./tmp/#{name}.dot", "w+") do |output|
16
+ RubyProf::DotPrinter.new(result).print(output, min_percent: 0)
17
+ end
18
+
19
+ FileUtils.touch "./tmp/#{name}.html"
20
+ File.open("./tmp/#{name}.html", "w+") do |output|
21
+ RubyProf::CallStackPrinter.new(result).print(output, min_percent: 0)
22
+ end
23
+
24
+ system "dot -Tpng ./tmp/#{name}.dot > ./tmp/#{name}.png"
25
+ end
26
+
27
+ desc "Profiles initialization with required param and option"
28
+ task :required do
29
+ profile("required", -> { User.new :Andy, email: "andy@example.com" }) do
30
+ class User
31
+ extend Dry::Initializer
32
+ param :name
33
+ option :email
34
+ end
35
+ end
36
+ end
37
+
38
+ desc "Profiles initialization with default param and option"
39
+ task :defaults do
40
+ profile("defaults", -> { User.new }) do
41
+ class User
42
+ extend Dry::Initializer
43
+ param :name, default: -> { :Andy }
44
+ option :email, default: -> { "andy@example.com" }
45
+ end
46
+ end
47
+ end
48
+
49
+ desc "Profiles initialization with coerced param and option"
50
+ task :coercion do
51
+ profile("coercion", -> { User.new :Andy, email: :"andy@example.com" }) do
52
+ class User
53
+ extend Dry::Initializer
54
+ param :name, proc(&:to_s)
55
+ option :email, proc(&:to_s)
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "Profiles initialization with coerced defaults of param and option"
61
+ task :default_coercion do
62
+ profile("default_coercion", -> { User.new }) do
63
+ class User
64
+ extend Dry::Initializer
65
+ param :name, proc(&:to_s), default: -> { :Andy }
66
+ option :email, proc(&:to_s), default: -> { :"andy@example.com" }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ desc "Makes all profiling at once"
73
+ task profile: %i[
74
+ profile:required
75
+ profile:defaults
76
+ profile:coercion
77
+ profile:default_coercion
78
+ ]