module NRSER::Types::Factory
Mixin that provides {#def_type} to create type factory class methods.
Mixed in to {NRSER::Types}, but can also be mixed in by libraries using the types system to define their own types.
Public Instance Methods
def_factory(name, maybe: true, aliases: [], &body)
click to toggle source
Define a type factory.
@deprecated Use {#def_type}
# File lib/nrser/types/factory.rb, line 38 def def_factory name, maybe: true, aliases: [], &body define_singleton_method name, &body aliases.each do |alias_name| if self.respond_to? alias_name alias_name = alias_name.to_s + '_' end singleton_class.send :alias_method, alias_name, name end if maybe && !name.to_s.end_with?( '?' ) maybe_name = "#{ name }?".to_sym if self.respond_to? maybe_name maybe_name = "#{ name }_?".to_sym end # HACK Ugh maybe I wrote this quick to fix it, not sure if it's a decent # idea.. basically, need to figure out what `options` keys go # to {.maybe} and which ones go to the regular factory... matters # for shit like {.attrs} and {.hash_type} 'cause they use option # keys (whether they *should* is something I've debated... sigh, # it is what it is for now). # # So they options that go to {.maybe} just go strait through to # {Type#initialize}, so just grab that method, see what keys it # takes, and then can slice and dice off that... # maybe_option_keys = Set.new \ NRSER::Types::Type. instance_method( :initialize ). parameters. select { |param_type, name| param_type == :key }. map { |param_type, name| name } define_singleton_method maybe_name do |*args, **options| maybe_options = options.slice *maybe_option_keys factory_options = options.except *maybe_option_keys NRSER::Types.maybe \ public_send( name, *args, **factory_options ), **maybe_options end aliases.each do |alias_name| maybe_alias_name = "#{ alias_name }?" if self.respond_to? maybe_alias_name maybe_alias_name = "#{ alias_name }_?" end singleton_class.send :alias_method, maybe_alias_name, maybe_name end end end
def_type(name, aliases: [], from_s: nil, maybe: true, default_name: nil, parameterize: nil, symbolic: nil, to_data: nil, &body)
click to toggle source
Define a new type factory class method.
@param [#to_s] name
The name of the type. Will be normalized to a string via it's `#to_s` method.
@param [Enumerable<#to_s>] aliases
Aliases to add for the type factory method. Normalized to a {Set} of strings before use.
@param [nil | Proc<(s:String): MEMBER>] from_s
Optional function to load type members from strings.
@param [Boolean] maybe
When `true` will add `?`-suffixed versions of the factory that create {.Maybe} versions of the type.
@param [nil | false | Proc<(*args, &block): String>] default_name
Controls the default value assigned to the `name:` keyword argument (only relevant when a `name:` value is *not* explicitly passed when building the type). Everything here is done *before* the `options` are passed to the factory method's `&body`, so the body will see the default `name:` value in the factory method body. When... - `default_name == nil` Behavior depends on the value of the `parameterize:` keyword: - `parameterize == nil` The `name` parameter will be used as the default value. This situation covers "static" types that will only differ by their `options` (custom {Type#from_s}, {Type#to_data}, etc.). Really, these are more like aliases since the types' member sets are identical. - `parameterize != nil` The default `name:` value will be left as `nil`. - `default_name == false` The `name:` option will not be touched - it will stay `nil` unless the factory caller provides a value. - `default_name is a Proc<(*args, &block)->String>` When the factory caller does not provide a `name:` option this function will be called with the arguments (including `options`) and block (if any) that the factory method was called with, and is expected to return a {String} that will be set as the `name:` option.
@param [nil | Symbol
| Array
<Symbol>] parameterize
Indicates if the type is parameterized, and, if so, what arguments it's parameterized over. Right now, just prevents the `name:` being assigned as the type's name when one isn't specified (see the `default_name:` parameter a complete(-ly confusing) explanation. The hope was to use this for something useful in the future, but who the hell knows to be honest.
@param [nil | String
| Proc<(*args, &block): String>] symbolic
Controls what's done with the `symbolic:` option - which affects what the new type's {Type#symbolic} will return - when the factory methods are called with the `symbolic:` option `nil` or missing: - `nil` - nothing changes. `nil` goes in to the type initialization method, and should end up as a `symbolic: nil` option in {Type#initialize}. - `String` - This value is used. Makes sense for `static` types who only accept options that don't affect the members of the types they produce. - `Proc<(*args, &block)->String>` - Gets called with the arguments (including `options`) and block (if any) the factory method is called with and is expected to return the symbolic string representation.
@param [nil | Proc<(MEMBER): DATA>] to_data
I'm getting tired of writing this shit so I'm going to be brief here - provides a value that will get set as the `to_data:` option and become responsible for turning type member values into "data" (think things you can JSON encode).
@param [Proc] body
The type factory method body. **MUST** return a {Type} instance.
@return [nil]
Just creates class methods on whatever it's mixed in to.
# File lib/nrser/types/factory.rb, line 195 def def_type name, aliases: [], from_s: nil, maybe: true, default_name: nil, parameterize: nil, symbolic: nil, to_data: nil, &body # Normalize to strings name = name.to_s aliases = aliases.map( &:to_s ).to_set unless default_name.nil? || default_name == false || default_name.is_a?( Proc ) raise NRSER::TypeError.new \ "`default_name:` keyword argument must be {nil}, {false} or a {Proc},", "found", default_name.inspect, expected: [ nil, false, Proc ], received: default_name end # Count the required params so we know if we can take the last one as # options or not. # # For this to work, {#def_type} has to be called like # # def_type name, # &->( arg, **option ) do # # ... # end # # because the `do |arg, **option|` form marks *all* arguments as optional. # num_req_params = body.parameters.count { |type, name| type == :req } define_singleton_method name, &->(*args, &block) do if args.length > num_req_params && args[-1].is_a?( Hash ) && args[-1].keys.all? { |k| k.is_a? Symbol } options = args[-1] args = args[0..-2] else options = {} end if args.length < num_req_params raise ArgumentError, "wrong number of arguments (given #{ args.length }, " + "expected #{ num_req_params })" end # If `default_name` is {false} it means we don't fuck with the name at # all, and if it's not `nil` it's been user-set. if options[:name].nil? && default_name != false if default_name.is_a? Proc options[:name] = default_name.call *args, &block # The "old" (like, two days ago) way of signalling not to write `name` # in (before we had `default_name=false`) was to tell {#def_type} that # you were parameterizing, in which case it wouldn't make any sense # to write `name` in for all the types coming out. # # And it still doesn't, though - despite high hopes for a future of # parameterized enlightenment - that's all we've been using # `parameterize` for at the time, and there will def be some argument # structure kinks to work out in order to actually do something useful # with the information, though I'm sure that is solvable. # # So I'm saying I wouldn't be surprised if `parameterize` ended up # never really going anywhere except away. elsif parameterize.nil? options[:name] = name end end # if options[:name].nil? && default_name != false options[:from_s] ||= from_s options[:symbolic] ||= case symbolic when Proc symbolic.call *args, &block else symbolic end options[:to_data] ||= to_data body.call( *args, **options, &block ).tap { |type| unless type.is_a? Type raise NRSER::TypeError.new \ "Type factory method #{ self.safe_name }.#{ __method__ } did", "not return a {NRSER::Types::Type}! All type factory methods", "**MUST** always return type instances. This method needs to be", "fixed." end } end underscored = name.underscore # Underscored names are also available! unless name == underscored || aliases.include?( underscored ) aliases << underscored end aliases.each do |alias_name| if self.respond_to? alias_name alias_name = alias_name.to_s + '_' end singleton_class.send :alias_method, alias_name, name end if maybe && !name.end_with?( '?' ) maybe_name = "#{ name }?" if self.respond_to? maybe_name maybe_name = "#{ name }_?" end # HACK Ugh maybe I wrote this quick to fix it, not sure if it's a decent # idea.. basically, need to figure out what `options` keys go # to {.maybe} and which ones go to the regular factory... matters # for shit like {.attrs} and {.hash_type} 'cause they use option # keys (whether they *should* is something I've debated... sigh, # it is what it is for now). # # So the options that go to {.maybe} just go strait through to # {Type#initialize}, so just grab that method, see what keys it # takes, and then can slice and dice off that... # maybe_option_keys = Set.new \ NRSER::Types::Type. instance_method( :initialize ). parameters. select { |param_type, name| param_type == :key }. map { |param_type, name| name } define_singleton_method maybe_name do |*args, **options| maybe_options = options.slice *maybe_option_keys factory_options = options.except *maybe_option_keys NRSER::Types.maybe \ public_send( name, *args, **factory_options ), **maybe_options end aliases.each do |alias_name| maybe_alias_name = "#{ alias_name }?" if self.respond_to? maybe_alias_name maybe_alias_name = "#{ alias_name }_?" end singleton_class.send :alias_method, maybe_alias_name, maybe_name end end nil end