# frozen_string_literal: true # The module is responsible for __normalizing__ arguments # of `.param` and `.option`. # # What the module does is convert the source list of arguments # into the standard set of options: # - `:option` -- whether an argument is an option (or param) # - `:source` -- the name of source option # - `:target` -- the target name of the reader # - `:reader` -- if the reader's privacy (:public, :protected, :private, nil) # - `:ivar` -- the target nane of the variable # - `:type` -- the callable coercer of the source value # - `:optional` -- if the argument is optional # - `:default` -- the proc returning the default value of the source value # - `:null` -- the value to be set to unassigned optional argument # # It is this set is used to build [Dry::Initializer::Definition]. # # @example # # from `option :foo, [], as: :bar, optional: :true # input = { name: :foo, as: :bar, type: [], optional: true } # # Dry::Initializer::Dispatcher.call(input) # # => { # # source: "foo", # # target: "bar", # # reader: :public, # # ivar: "@bar", # # type: ->(v) { Array(v) } }, # simplified for brevity # # optional: true, # # default: -> { Dry::Initializer::UNDEFINED }, # # } # # # Settings # # The module uses global setting `null` to define what value # should be set to variables that kept unassigned. By default it # uses `Dry::Initializer::UNDEFINED` # # # Syntax Extensions # # The module supports syntax extensions. You can add any number # of custom dispatchers __on top__ of the stack of default dispatchers. # Every dispatcher should be a callable object that takes # the source set of options and converts it to another set of options. # # @example Add special dispatcher # # # Define a dispatcher for key :integer # dispatcher = proc do |integer: false, **opts| # opts.merge(type: proc(&:to_i)) if integer # end # # # Register a dispatcher # Dry::Initializer::Dispatchers << dispatcher # # # Now you can use option `integer: true` instead of `type: proc(&:to_i)` # class Foo # extend Dry::Initializer # param :id, integer: true # end # module Dry module Initializer module Dispatchers extend self # @!attribute [rw] null Defines a value to be set to unassigned attributes # @return [Object] attr_accessor :null # # Registers a new dispatcher # # @param [#call] dispatcher # @return [self] itself # def <<(dispatcher) @pipeline = [dispatcher] + pipeline self end # # Normalizes the source set of options # # @param [Hash] options # @return [Hash] normalized set of options # def call(**options) options = {null: null, **options} pipeline.reduce(options) { |opts, dispatcher| dispatcher.call(**opts) } end private require_relative "dispatchers/build_nested_type" require_relative "dispatchers/check_type" require_relative "dispatchers/prepare_default" require_relative "dispatchers/prepare_ivar" require_relative "dispatchers/prepare_optional" require_relative "dispatchers/prepare_reader" require_relative "dispatchers/prepare_source" require_relative "dispatchers/prepare_target" require_relative "dispatchers/unwrap_type" require_relative "dispatchers/wrap_type" def pipeline @pipeline ||= [ PrepareSource, PrepareTarget, PrepareIvar, PrepareReader, PrepareDefault, PrepareOptional, UnwrapType, CheckType, BuildNestedType, WrapType ] end end end end