rom-mapper 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +19 -13
  5. data/{Changelog.md → CHANGELOG.md} +6 -0
  6. data/Gemfile +23 -10
  7. data/README.md +17 -12
  8. data/Rakefile +12 -4
  9. data/lib/rom-mapper.rb +6 -15
  10. data/lib/rom/header.rb +195 -0
  11. data/lib/rom/header/attribute.rb +184 -0
  12. data/lib/rom/mapper.rb +63 -100
  13. data/lib/rom/mapper/attribute_dsl.rb +477 -0
  14. data/lib/rom/mapper/dsl.rb +120 -0
  15. data/lib/rom/mapper/model_dsl.rb +55 -0
  16. data/lib/rom/mapper/version.rb +3 -7
  17. data/lib/rom/model_builder.rb +99 -0
  18. data/lib/rom/processor.rb +28 -0
  19. data/lib/rom/processor/transproc.rb +388 -0
  20. data/rakelib/benchmark.rake +15 -0
  21. data/rakelib/mutant.rake +16 -0
  22. data/rakelib/rubocop.rake +18 -0
  23. data/rom-mapper.gemspec +7 -6
  24. data/spec/spec_helper.rb +32 -33
  25. data/spec/support/constant_leak_finder.rb +14 -0
  26. data/spec/support/mutant.rb +10 -0
  27. data/spec/unit/rom/mapper/dsl_spec.rb +467 -0
  28. data/spec/unit/rom/mapper_spec.rb +83 -0
  29. data/spec/unit/rom/processor/transproc_spec.rb +448 -0
  30. metadata +68 -89
  31. data/.ruby-version +0 -1
  32. data/Gemfile.devtools +0 -55
  33. data/config/devtools.yml +0 -2
  34. data/config/flay.yml +0 -3
  35. data/config/flog.yml +0 -2
  36. data/config/mutant.yml +0 -3
  37. data/config/reek.yml +0 -103
  38. data/config/rubocop.yml +0 -45
  39. data/lib/rom/mapper/attribute.rb +0 -31
  40. data/lib/rom/mapper/dumper.rb +0 -27
  41. data/lib/rom/mapper/loader.rb +0 -22
  42. data/lib/rom/mapper/loader/allocator.rb +0 -32
  43. data/lib/rom/mapper/loader/attribute_writer.rb +0 -23
  44. data/lib/rom/mapper/loader/object_builder.rb +0 -28
  45. data/spec/shared/unit/loader_call.rb +0 -13
  46. data/spec/shared/unit/loader_identity.rb +0 -13
  47. data/spec/shared/unit/mapper_context.rb +0 -13
  48. data/spec/unit/rom/mapper/call_spec.rb +0 -32
  49. data/spec/unit/rom/mapper/class_methods/build_spec.rb +0 -64
  50. data/spec/unit/rom/mapper/dump_spec.rb +0 -15
  51. data/spec/unit/rom/mapper/dumper/call_spec.rb +0 -29
  52. data/spec/unit/rom/mapper/dumper/identity_spec.rb +0 -28
  53. data/spec/unit/rom/mapper/header/each_spec.rb +0 -28
  54. data/spec/unit/rom/mapper/header/element_reader_spec.rb +0 -25
  55. data/spec/unit/rom/mapper/header/keys_spec.rb +0 -32
  56. data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +0 -15
  57. data/spec/unit/rom/mapper/identity_spec.rb +0 -15
  58. data/spec/unit/rom/mapper/load_spec.rb +0 -15
  59. data/spec/unit/rom/mapper/loader/allocator/call_spec.rb +0 -7
  60. data/spec/unit/rom/mapper/loader/allocator/identity_spec.rb +0 -7
  61. data/spec/unit/rom/mapper/loader/attribute_writer/call_spec.rb +0 -7
  62. data/spec/unit/rom/mapper/loader/attribute_writer/identity_spec.rb +0 -7
  63. data/spec/unit/rom/mapper/loader/object_builder/call_spec.rb +0 -7
  64. data/spec/unit/rom/mapper/loader/object_builder/identity_spec.rb +0 -7
  65. data/spec/unit/rom/mapper/model_spec.rb +0 -11
  66. data/spec/unit/rom/mapper/new_object_spec.rb +0 -14
@@ -0,0 +1,120 @@
1
+ require 'rom/support/class_macros'
2
+ require 'rom/mapper/attribute_dsl'
3
+
4
+ module ROM
5
+ class Mapper
6
+ # Mapper class-level DSL including Attribute DSL and Model DSL
7
+ module DSL
8
+ # Extend mapper class with macros and DSL methods
9
+ #
10
+ # @api private
11
+ def self.included(klass)
12
+ klass.extend(ClassMacros)
13
+ klass.extend(ClassMethods)
14
+ end
15
+
16
+ # Class methods for all mappers
17
+ #
18
+ # @private
19
+ module ClassMethods
20
+ # Set base ivars for the mapper class
21
+ #
22
+ # @api private
23
+ def inherited(klass)
24
+ super
25
+
26
+ klass.instance_variable_set('@attributes', nil)
27
+ klass.instance_variable_set('@header', nil)
28
+ klass.instance_variable_set('@dsl', nil)
29
+ end
30
+
31
+ # include a registered plugin in this mapper
32
+ #
33
+ # @param [Symbol] plugin
34
+ # @param [Hash] options
35
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
36
+ #
37
+ # @api public
38
+ def use(plugin, options = {})
39
+ adapter = options.fetch(:adapter, :default)
40
+
41
+ ROM.plugin_registry.mappers.fetch(plugin, adapter).apply_to(self)
42
+ end
43
+
44
+ # Return base_relation used for creating mapper registry
45
+ #
46
+ # This is used to "gather" mappers under same root name
47
+ #
48
+ # @api private
49
+ def base_relation
50
+ if superclass.relation
51
+ superclass.relation
52
+ else
53
+ relation
54
+ end
55
+ end
56
+
57
+ # Return header of the mapper
58
+ #
59
+ # This is memoized so mutating mapper class won't have an effect wrt
60
+ # header after it was initialized for the first time.
61
+ #
62
+ # TODO: freezing mapper class here is probably a good idea
63
+ #
64
+ # @api private
65
+ def header
66
+ @header ||= dsl.header
67
+ end
68
+
69
+ # @api private
70
+ def respond_to_missing?(name, _include_private = false)
71
+ dsl.respond_to?(name) || super
72
+ end
73
+
74
+ private
75
+
76
+ # Return default Attribute DSL options based on settings of the mapper
77
+ # class
78
+ #
79
+ # @api private
80
+ def options
81
+ { prefix: prefix,
82
+ prefix_separator: prefix_separator,
83
+ symbolize_keys: symbolize_keys,
84
+ reject_keys: reject_keys }
85
+ end
86
+
87
+ # Return default attributes that might have been inherited from the
88
+ # superclass
89
+ #
90
+ # @api private
91
+ def attributes
92
+ @attributes ||=
93
+ if superclass.respond_to?(:attributes, true) && inherit_header
94
+ superclass.attributes.dup
95
+ else
96
+ []
97
+ end
98
+ end
99
+
100
+ # Create the attribute DSL instance used by the mapper class
101
+ #
102
+ # @api private
103
+ def dsl
104
+ @dsl ||= AttributeDSL.new(attributes, options)
105
+ end
106
+
107
+ # Delegate Attribute DSL method to the dsl instance
108
+ #
109
+ # @api private
110
+ def method_missing(name, *args, &block)
111
+ if dsl.respond_to?(name)
112
+ dsl.public_send(name, *args, &block)
113
+ else
114
+ super
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,55 @@
1
+ require 'rom/model_builder'
2
+
3
+ module ROM
4
+ class Mapper
5
+ # Model DSL allows setting a model class
6
+ #
7
+ # @private
8
+ module ModelDSL
9
+ attr_reader :attributes, :builder, :klass
10
+
11
+ DEFAULT_TYPE = :poro
12
+
13
+ # Set or generate a model
14
+ #
15
+ # @example
16
+ # class MyDefinition
17
+ # include ROM::Mapper::ModelDSL
18
+ #
19
+ # def initialize
20
+ # @attributes = [[:name], [:title]]
21
+ # end
22
+ # end
23
+ #
24
+ # definition = MyDefinition.new
25
+ #
26
+ # # just set a model constant
27
+ # definition.model(User)
28
+ #
29
+ # # generate model class for the attributes
30
+ # definition.model(name: 'User')
31
+ #
32
+ # @api public
33
+ def model(options = nil)
34
+ if options.is_a?(Class)
35
+ @klass = options
36
+ elsif options
37
+ type = options.fetch(:type) { DEFAULT_TYPE }
38
+ @builder = ModelBuilder[type].new(options)
39
+ end
40
+
41
+ build_class unless options
42
+ end
43
+
44
+ private
45
+
46
+ # Build a model class using a specialized builder
47
+ #
48
+ # @api private
49
+ def build_class
50
+ return klass if klass
51
+ return builder.call(attributes.map(&:first)) if builder
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,9 +1,5 @@
1
- # encoding: utf-8
2
-
3
1
  module ROM
4
2
  class Mapper
5
-
6
- VERSION = '0.1.1'.freeze
7
-
8
- end # Mapper
9
- end # ROM
3
+ VERSION = '0.2.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ module ROM
2
+ # Model builders can be used to build model classes for mappers
3
+ #
4
+ # This is used when you define a mapper and setup a model using :name option.
5
+ #
6
+ # @example
7
+ # # this will define User model for you
8
+ # class UserMapper < ROM::Mapper
9
+ # model name: 'User'
10
+ # attribute :id
11
+ # attribute :name
12
+ # end
13
+ #
14
+ # @private
15
+ class ModelBuilder
16
+ attr_reader :name
17
+
18
+ attr_reader :const_name, :namespace, :klass
19
+
20
+ # Return model builder subclass based on type
21
+ #
22
+ # @param [Symbol] type
23
+ #
24
+ # @return [Class]
25
+ #
26
+ # @api private
27
+ def self.[](type)
28
+ case type
29
+ when :poro then PORO
30
+ else
31
+ raise ArgumentError, "#{type.inspect} is not a supported model type"
32
+ end
33
+ end
34
+
35
+ # Build a model class
36
+ #
37
+ # @return [Class]
38
+ #
39
+ # @api private
40
+ def self.call(*args)
41
+ new(*args).call
42
+ end
43
+
44
+ # @api private
45
+ def initialize(options = {})
46
+ @name = options[:name]
47
+
48
+ if name
49
+ parts = name.split('::')
50
+
51
+ @const_name = parts.pop
52
+
53
+ @namespace =
54
+ if parts.any?
55
+ Inflector.constantize(parts.join('::'))
56
+ else
57
+ Object
58
+ end
59
+ end
60
+ end
61
+
62
+ # Define a model class constant
63
+ #
64
+ # @api private
65
+ def define_const
66
+ namespace.const_set(const_name, klass)
67
+ end
68
+
69
+ # Build a model class supporting specific attributes
70
+ #
71
+ # @return [Class]
72
+ #
73
+ # @api private
74
+ def call(attrs)
75
+ define_class(attrs)
76
+ define_const if const_name
77
+ @klass
78
+ end
79
+
80
+ # PORO model class builder
81
+ #
82
+ # @private
83
+ class PORO < ModelBuilder
84
+ def define_class(attrs)
85
+ @klass = Class.new
86
+
87
+ @klass.send(:attr_reader, *attrs)
88
+
89
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
90
+ def initialize(params)
91
+ #{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
92
+ end
93
+ RUBY
94
+
95
+ self
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,28 @@
1
+ require 'rom/mapper'
2
+
3
+ module ROM
4
+ # Abstract processor class
5
+ #
6
+ # Every ROM processor should inherit from this class
7
+ #
8
+ # @api public
9
+ class Processor
10
+ # Hook used to auto-register a processor class
11
+ #
12
+ # @api private
13
+ def self.inherited(processor)
14
+ Mapper.register_processor(processor)
15
+ end
16
+
17
+ # Required interface to be implemented by descendants
18
+ #
19
+ # @return [Processor]
20
+ #
21
+ # @abstract
22
+ #
23
+ # @api private
24
+ def self.build
25
+ raise NotImplementedError, "+build+ must be implemented"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,388 @@
1
+ require 'transproc/all'
2
+
3
+ require 'rom/processor'
4
+
5
+ module ROM
6
+ class Processor
7
+ # Data mapping transformer builder using Transproc
8
+ #
9
+ # This builds a transproc function that is used to map a whole relation
10
+ #
11
+ # @see https://github.com/solnic/transproc too
12
+ #
13
+ # @private
14
+ class Transproc < Processor
15
+ include ::Transproc::Composer
16
+
17
+ module Functions
18
+ extend ::Transproc::Registry
19
+
20
+ import ::Transproc::Coercions
21
+ import ::Transproc::ArrayTransformations
22
+ import ::Transproc::HashTransformations
23
+ import ::Transproc::ClassTransformations
24
+
25
+ def self.identity(tuple)
26
+ tuple
27
+ end
28
+
29
+ def self.filter_empty(arr)
30
+ arr.reject { |row| row.values.all?(&:nil?) }
31
+ end
32
+ end
33
+
34
+ # @return [Header] header from a mapper
35
+ #
36
+ # @api private
37
+ attr_reader :header
38
+
39
+ # @return [Class] model class from a mapper
40
+ #
41
+ # @api private
42
+ attr_reader :model
43
+
44
+ # @return [Hash] header's attribute mapping
45
+ #
46
+ # @api private
47
+ attr_reader :mapping
48
+
49
+ # @return [Proc] row-processing proc
50
+ #
51
+ # @api private
52
+ attr_reader :row_proc
53
+
54
+ # Build a transproc function from the header
55
+ #
56
+ # @param [ROM::Header] header
57
+ #
58
+ # @return [Transproc::Function]
59
+ #
60
+ # @api private
61
+ def self.build(header)
62
+ new(header).to_transproc
63
+ end
64
+
65
+ # @api private
66
+ def initialize(header)
67
+ @header = header
68
+ @model = header.model
69
+ @mapping = header.mapping
70
+ initialize_row_proc
71
+ end
72
+
73
+ # Coerce mapper header to a transproc data mapping function
74
+ #
75
+ # @return [Transproc::Function]
76
+ #
77
+ # @api private
78
+ def to_transproc
79
+ compose(t(:identity)) do |ops|
80
+ combined = header.combined
81
+ ops << t(:combine, combined.map(&method(:combined_args))) if combined.any?
82
+ ops << header.preprocessed.map { |attr| visit(attr, true) }
83
+ ops << t(:map_array, row_proc) if row_proc
84
+ ops << header.postprocessed.map { |attr| visit(attr, true) }
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Visit an attribute from the header
91
+ #
92
+ # This forwards to a specialized visitor based on the attribute type
93
+ #
94
+ # @param [Header::Attribute] attribute
95
+ # @param [Array] args Allows to send `preprocess: true`
96
+ #
97
+ # @api private
98
+ def visit(attribute, *args)
99
+ type = attribute.class.name.split('::').last.downcase
100
+ send("visit_#{type}", attribute, *args)
101
+ end
102
+
103
+ # Visit plain attribute
104
+ #
105
+ # It will call block transformation if it's used
106
+ #
107
+ # If it's a typed attribute a coercion transformation is added
108
+ #
109
+ # @param [Header::Attribute] attribute
110
+ #
111
+ # @api private
112
+ def visit_attribute(attribute)
113
+ coercer = attribute.meta[:coercer]
114
+ if coercer
115
+ t(:map_value, attribute.name, coercer)
116
+ elsif attribute.typed?
117
+ t(:map_value, attribute.name, t(:"to_#{attribute.type}"))
118
+ end
119
+ end
120
+
121
+ # Visit hash attribute
122
+ #
123
+ # @param [Header::Attribute::Hash] attribute
124
+ #
125
+ # @api private
126
+ def visit_hash(attribute)
127
+ with_row_proc(attribute) do |row_proc|
128
+ t(:map_value, attribute.name, row_proc)
129
+ end
130
+ end
131
+
132
+ # Visit combined attribute
133
+ #
134
+ # @api private
135
+ def visit_combined(attribute)
136
+ op = with_row_proc(attribute) do |row_proc|
137
+ array_proc =
138
+ if attribute.type == :hash
139
+ t(:map_array, row_proc) >> -> arr { arr.first }
140
+ else
141
+ t(:map_array, row_proc)
142
+ end
143
+
144
+ t(:map_value, attribute.name, array_proc)
145
+ end
146
+
147
+ if op
148
+ op
149
+ elsif attribute.type == :hash
150
+ t(:map_value, attribute.name, -> arr { arr.first })
151
+ end
152
+ end
153
+
154
+ # Visit array attribute
155
+ #
156
+ # @param [Header::Attribute::Array] attribute
157
+ #
158
+ # @api private
159
+ def visit_array(attribute)
160
+ with_row_proc(attribute) do |row_proc|
161
+ t(:map_value, attribute.name, t(:map_array, row_proc))
162
+ end
163
+ end
164
+
165
+ # Visit wrapped hash attribute
166
+ #
167
+ # :nest transformation is added to handle wrapping
168
+ #
169
+ # @param [Header::Attribute::Wrap] attribute
170
+ #
171
+ # @api private
172
+ def visit_wrap(attribute)
173
+ name = attribute.name
174
+ keys = attribute.tuple_keys
175
+
176
+ compose do |ops|
177
+ ops << t(:nest, name, keys)
178
+ ops << visit_hash(attribute)
179
+ end
180
+ end
181
+
182
+ # Visit unwrap attribute
183
+ #
184
+ # :unwrap transformation is added to handle unwrapping
185
+ #
186
+ # @param [Header::Attributes::Unwrap]
187
+ #
188
+ # @api private
189
+ def visit_unwrap(attribute)
190
+ name = attribute.name
191
+ keys = attribute.pop_keys
192
+
193
+ compose do |ops|
194
+ ops << visit_hash(attribute)
195
+ ops << t(:unwrap, name, keys)
196
+ end
197
+ end
198
+
199
+ # Visit group hash attribute
200
+ #
201
+ # :group transformation is added to handle grouping during preprocessing.
202
+ # Otherwise we simply use array visitor for the attribute.
203
+ #
204
+ # @param [Header::Attribute::Group] attribute
205
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
206
+ # function that is applied to the whole relation
207
+ #
208
+ # @api private
209
+ def visit_group(attribute, preprocess = false)
210
+ if preprocess
211
+ name = attribute.name
212
+ header = attribute.header
213
+ keys = attribute.tuple_keys
214
+
215
+ others = header.preprocessed
216
+
217
+ compose do |ops|
218
+ ops << t(:group, name, keys)
219
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
220
+ ops << others.map { |attr|
221
+ t(:map_array, t(:map_value, name, visit(attr, true)))
222
+ }
223
+ end
224
+ else
225
+ visit_array(attribute)
226
+ end
227
+ end
228
+
229
+ # Visit ungroup attribute
230
+ #
231
+ # :ungroup transforation is added to handle ungrouping during preprocessing.
232
+ # Otherwise we simply use array visitor for the attribute.
233
+ #
234
+ # @param [Header::Attribute::Ungroup] attribute
235
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
236
+ # function that is applied to the whole relation
237
+ #
238
+ # @api private
239
+ def visit_ungroup(attribute, preprocess = false)
240
+ if preprocess
241
+ name = attribute.name
242
+ header = attribute.header
243
+ keys = attribute.pop_keys
244
+
245
+ others = header.postprocessed
246
+
247
+ compose do |ops|
248
+ ops << others.map { |attr|
249
+ t(:map_array, t(:map_value, name, visit(attr, true)))
250
+ }
251
+ ops << t(:ungroup, name, keys)
252
+ end
253
+ else
254
+ visit_array(attribute)
255
+ end
256
+ end
257
+
258
+ # Visit fold hash attribute
259
+ #
260
+ # :fold transformation is added to handle folding during preprocessing.
261
+ #
262
+ # @param [Header::Attribute::Fold] attribute
263
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
264
+ # function that is applied to the whole relation
265
+ #
266
+ # @api private
267
+ def visit_fold(attribute, preprocess = false)
268
+ if preprocess
269
+ name = attribute.name
270
+ keys = attribute.tuple_keys
271
+
272
+ compose do |ops|
273
+ ops << t(:group, name, keys)
274
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
275
+ ops << t(:map_array, t(:fold, name, keys.first))
276
+ end
277
+ end
278
+ end
279
+
280
+ # Visit unfold hash attribute
281
+ #
282
+ # :unfold transformation is added to handle unfolding during preprocessing.
283
+ #
284
+ # @param [Header::Attribute::Unfold] attribute
285
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
286
+ # function that is applied to the whole relation
287
+ #
288
+ # @api private
289
+ def visit_unfold(attribute, preprocess = false)
290
+ if preprocess
291
+ name = attribute.name
292
+ header = attribute.header
293
+ keys = attribute.pop_keys
294
+ key = keys.first
295
+
296
+ others = header.postprocessed
297
+
298
+ compose do |ops|
299
+ ops << others.map { |attr|
300
+ t(:map_array, t(:map_value, name, visit(attr, true)))
301
+ }
302
+ ops << t(:map_array, t(:map_value, name, t(:insert_key, key)))
303
+ ops << t(:map_array, t(:reject_keys, [key] - [name]))
304
+ ops << t(:ungroup, name, [key])
305
+ end
306
+ end
307
+ end
308
+
309
+ # Visit excluded attribute
310
+ #
311
+ # @param [Header::Attribute::Exclude] attribute
312
+ #
313
+ # @api private
314
+ def visit_exclude(attribute)
315
+ t(:reject_keys, [attribute.name])
316
+ end
317
+
318
+ # @api private
319
+ def combined_args(attribute)
320
+ other = attribute.header.combined
321
+
322
+ if other.any?
323
+ children = other.map(&method(:combined_args))
324
+ [attribute.name, attribute.meta[:keys], children]
325
+ else
326
+ [attribute.name, attribute.meta[:keys]]
327
+ end
328
+ end
329
+
330
+ # Build row_proc
331
+ #
332
+ # This transproc function is applied to each row in a dataset
333
+ #
334
+ # @api private
335
+ def initialize_row_proc
336
+ @row_proc = compose { |ops|
337
+ process_header_keys(ops)
338
+
339
+ ops << t(:rename_keys, mapping) if header.aliased?
340
+ ops << header.map { |attr| visit(attr) }
341
+ ops << t(:constructor_inject, model) if model
342
+ }
343
+ end
344
+
345
+ # Process row_proc header keys
346
+ #
347
+ # @api private
348
+ def process_header_keys(ops)
349
+ if header.reject_keys
350
+ all_keys = header.tuple_keys + header.non_primitives.map(&:key)
351
+ ops << t(:accept_keys, all_keys)
352
+ end
353
+ ops
354
+ end
355
+
356
+ # Yield row proc for a given attribute if any
357
+ #
358
+ # @param [Header::Attribute] attribute
359
+ #
360
+ # @api private
361
+ def with_row_proc(attribute)
362
+ row_proc = row_proc_from(attribute)
363
+ yield(row_proc) if row_proc
364
+ end
365
+
366
+ # Build a row_proc from a given attribute
367
+ #
368
+ # This is used by embedded attribute visitors
369
+ #
370
+ # @api private
371
+ def row_proc_from(attribute)
372
+ new(attribute.header).row_proc
373
+ end
374
+
375
+ # Return a new instance of the processor
376
+ #
377
+ # @api private
378
+ def new(*args)
379
+ self.class.new(*args)
380
+ end
381
+
382
+ # @api private
383
+ def t(*args)
384
+ Functions[*args]
385
+ end
386
+ end
387
+ end
388
+ end