Module: NRSER::Types::Factory

Included in:
NRSER::Types
Defined in:
lib/nrser/types/factory.rb

Overview

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.

Instance Method Summary collapse

Instance Method Details

#def_factory(name, maybe: true, aliases: [], &body) ⇒ Object

Deprecated.

Use #def_type

Define a type factory.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# 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) ⇒ nil

Define a new type factory class method.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/nrser/types/factory.rb', line 187

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,
      "`default_name:` keyword argument must be {nil}, {false} or a {Proc},",
      "found", default_name,
      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