class Puppet::Pops::Types::RubyGenerator

@api private

Constants

RUBY_RESERVED_WORDS
RUBY_RESERVED_WORDS_REVERSED

Public Class Methods

protect_reserved_name(name) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
35 def self.protect_reserved_name(name)
36   RUBY_RESERVED_WORDS[name] || name
37 end
unprotect_reserved_name(name) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
39 def self.unprotect_reserved_name(name)
40   RUBY_RESERVED_WORDS_REVERSED[name] || name
41 end

Public Instance Methods

array_type?(t) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
434 def array_type?(t)
435   case t
436   when PArrayType
437     true
438   when POptionalType
439     array_type?(t.optional_type)
440   when PNotUndefType
441     array_type?(t.type)
442   when PVariantType
443     t.types.all? { |v| array_type?(v) }
444   else
445     false
446   end
447 end
class_body(obj, segments, bld) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
201 def class_body(obj, segments, bld)
202   unless obj.parent.is_a?(PObjectType)
203     bld << "\n  include " << namespace_relative(segments, Puppet::Pops::Types::PuppetObject.name) << "\n\n" # marker interface
204     bld << "  def self.ref(type_string)\n"
205     bld << '    ' << namespace_relative(segments, Puppet::Pops::Types::PTypeReferenceType.name) << ".new(type_string)\n"
206     bld << "  end\n"
207   end
208 
209   # Output constants
210   constants, others = obj.attributes(true).values.partition { |a| a.kind == PObjectType::ATTRIBUTE_KIND_CONSTANT }
211   constants = constants.select { |ca| ca.container.equal?(obj) }
212   unless constants.empty?
213     constants.each { |ca| bld << "\n  def self." << rname(ca.name) << "\n    _pcore_type['" << ca.name << "'].value\n  end\n" }
214     constants.each { |ca| bld << "\n  def " << rname(ca.name) << "\n    self.class." << ca.name << "\n  end\n" }
215   end
216 
217   init_params = others.reject { |a| a.kind == PObjectType::ATTRIBUTE_KIND_DERIVED }
218   opt, non_opt = init_params.partition { |ip| ip.value? }
219   derived_attrs, obj_attrs = others.select { |a| a.container.equal?(obj) }.partition { |ip| ip.kind == PObjectType::ATTRIBUTE_KIND_DERIVED }
220 
221   include_type = obj.equality_include_type? && !(obj.parent.is_a?(PObjectType) && obj.parent.equality_include_type?)
222   if obj.equality.nil?
223     eq_names = obj_attrs.reject { |a| a.kind == PObjectType::ATTRIBUTE_KIND_CONSTANT }.map(&:name)
224   else
225     eq_names = obj.equality
226   end
227 
228   # Output type safe hash constructor
229   bld << "\n  def self.from_hash(init_hash)\n"
230   bld << '    from_asserted_hash(' << namespace_relative(segments, TypeAsserter.name) << '.assert_instance_of('
231   bld << "'" << obj.label << " initializer', _pcore_type.init_hash_type, init_hash))\n  end\n\n  def self.from_asserted_hash(init_hash)\n    new"
232   unless non_opt.empty? && opt.empty?
233     bld << "(\n"
234     non_opt.each { |ip| bld << "      init_hash['" << ip.name << "'],\n" }
235     opt.each do |ip|
236       if ip.value.nil?
237         bld << "      init_hash['" << ip.name << "'],\n"
238       else
239         bld << "      init_hash.fetch('" << ip.name << "') { "
240         default_string(bld, ip)
241         bld << " },\n"
242       end
243     end
244     bld.chomp!(",\n")
245     bld << ')'
246   end
247   bld << "\n  end\n"
248 
249   # Output type safe constructor
250   bld << "\n  def self.create"
251   if init_params.empty?
252     bld << "\n    new"
253   else
254     bld << '('
255     non_opt.each { |ip| bld << rname(ip.name) << ', ' }
256     opt.each do |ip|
257       bld << rname(ip.name) << ' = '
258       default_string(bld, ip)
259       bld << ', '
260     end
261     bld.chomp!(', ')
262     bld << ")\n"
263     bld << '    ta = ' << namespace_relative(segments, TypeAsserter.name) << "\n"
264     bld << "    attrs = _pcore_type.attributes(true)\n"
265     init_params.each do |a|
266       bld << "    ta.assert_instance_of('" << a.container.name << '[' << a.name << ']'
267       bld << "', attrs['" << a.name << "'].type, " << rname(a.name) << ")\n"
268     end
269     bld << '    new('
270     non_opt.each { |a| bld << rname(a.name) << ', ' }
271     opt.each { |a| bld << rname(a.name) << ', ' }
272     bld.chomp!(', ')
273     bld << ')'
274   end
275   bld << "\n  end\n"
276 
277   unless obj.parent.is_a?(PObjectType) && obj_attrs.empty?
278     # Output attr_readers
279     unless obj_attrs.empty?
280       bld << "\n"
281       obj_attrs.each { |a| bld << '  attr_reader :' << rname(a.name) << "\n" }
282     end
283 
284     bld << "  attr_reader :hash\n" if obj.parent.nil?
285 
286     derived_attrs.each do |a|
287       bld << "\n  def " << rname(a.name) << "\n"
288       code_annotation = RubyMethod.annotate(a)
289       ruby_body = code_annotation.nil? ? nil: code_annotation.body
290       if ruby_body.nil?
291         bld << "    raise Puppet::Error, \"no method is implemented for derived #{a.label}\"\n"
292       else
293         bld << '    ' << ruby_body << "\n"
294       end
295       bld << "  end\n"
296     end
297 
298     if init_params.empty?
299       bld << "\n  def initialize\n    @hash = " << obj.hash.to_s << "\n  end" if obj.parent.nil?
300     else
301       # Output initializer
302       bld << "\n  def initialize"
303       bld << '('
304       non_opt.each { |ip| bld << rname(ip.name) << ', ' }
305       opt.each do |ip|
306         bld << rname(ip.name) << ' = '
307         default_string(bld, ip)
308         bld << ', '
309       end
310       bld.chomp!(', ')
311       bld << ')'
312 
313       hash_participants = init_params.select { |ip| eq_names.include?(ip.name) }
314       if obj.parent.nil?
315         bld << "\n    @hash = "
316         bld << obj.hash.to_s << "\n" if hash_participants.empty?
317       else
318         bld << "\n    super("
319         super_args = (non_opt + opt).select { |ip| !ip.container.equal?(obj) }
320         unless super_args.empty?
321           super_args.each { |ip| bld << rname(ip.name) << ', ' }
322           bld.chomp!(', ')
323         end
324         bld << ")\n"
325         bld << '    @hash = @hash ^ ' unless hash_participants.empty?
326       end
327       unless hash_participants.empty?
328         hash_participants.each { |a| bld << rname(a.name) << '.hash ^ ' if a.container.equal?(obj) }
329         bld.chomp!(' ^ ')
330         bld << "\n"
331       end
332       init_params.each { |a| bld << '    @' << rname(a.name) << ' = ' << rname(a.name) << "\n" if a.container.equal?(obj) }
333       bld << "  end\n"
334     end
335   end
336 
337   unless obj_attrs.empty? && obj.parent.nil?
338     bld << "\n  def _pcore_init_hash\n"
339     bld << '    result = '
340     bld << (obj.parent.nil? ? '{}' : 'super')
341     bld << "\n"
342     obj_attrs.each do |a|
343       bld << "    result['" << a.name << "'] = @" << rname(a.name)
344       if a.value?
345         bld << ' unless '
346         equals_default_string(bld, a)
347       end
348       bld << "\n"
349     end
350     bld << "    result\n  end\n"
351   end
352 
353   content_participants = init_params.select { |a| content_participant?(a) }
354   if content_participants.empty?
355     unless obj.parent.is_a?(PObjectType)
356       bld << "\n  def _pcore_contents\n  end\n"
357       bld << "\n  def _pcore_all_contents(path)\n  end\n"
358     end
359   else
360     bld << "\n  def _pcore_contents\n"
361     content_participants.each do |cp|
362       if array_type?(cp.type)
363         bld << '    @' << rname(cp.name) << ".each { |value| yield(value) }\n"
364       else
365         bld << '    yield(@' << rname(cp.name) << ') unless @' << rname(cp.name)  << ".nil?\n"
366       end
367     end
368     bld << "  end\n\n  def _pcore_all_contents(path, &block)\n    path << self\n"
369     content_participants.each do |cp|
370       if array_type?(cp.type)
371         bld << '    @' << rname(cp.name) << ".each do |value|\n"
372         bld << "      block.call(value, path)\n"
373         bld << "      value._pcore_all_contents(path, &block)\n"
374       else
375         bld << '    unless @' << rname(cp.name) << ".nil?\n"
376         bld << '      block.call(@' << rname(cp.name) << ", path)\n"
377         bld << '      @' << rname(cp.name) << "._pcore_all_contents(path, &block)\n"
378       end
379       bld << "    end\n"
380     end
381     bld << "    path.pop\n  end\n"
382   end
383 
384   # Output function placeholders
385   obj.functions(false).each_value do |func|
386     code_annotation = RubyMethod.annotate(func)
387     if code_annotation
388       body = code_annotation.body
389       params = code_annotation.parameters
390       bld << "\n  def " << rname(func.name)
391       unless params.nil? || params.empty?
392         bld << '(' << params << ')'
393       end
394       bld << "\n    " << body << "\n"
395     else
396       bld << "\n  def " << rname(func.name) << "(*args)\n"
397       bld << "    # Placeholder for #{func.type}\n"
398       bld << "    raise Puppet::Error, \"no method is implemented for #{func.label}\"\n"
399     end
400     bld << "  end\n"
401   end
402 
403   unless eq_names.empty? && !include_type
404     bld << "\n  def eql?(o)\n"
405     bld << "    super &&\n" unless obj.parent.nil?
406     bld << "    o.instance_of?(self.class) &&\n" if include_type
407     eq_names.each { |eqn| bld << '    @' << rname(eqn) << '.eql?(o.' <<  rname(eqn) << ") &&\n" }
408     bld.chomp!(" &&\n")
409     bld << "\n  end\n  alias == eql?\n"
410   end
411 end
class_definition(obj, namespace_segments, bld, class_name, *impl_subst) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
165 def class_definition(obj, namespace_segments, bld, class_name, *impl_subst)
166   module_segments = remove_common_namespace(namespace_segments, class_name)
167   leaf_name = module_segments.pop
168   module_segments.each { |segment| bld << 'module ' << segment << "\n" }
169   scoped_class_definition(obj,leaf_name, bld, class_name, *impl_subst)
170   module_segments.size.times { bld << "end\n" }
171   module_segments << leaf_name
172   module_segments.join(TypeFormatter::NAME_SEGMENT_SEPARATOR)
173 end
content_participant?(a) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
413 def content_participant?(a)
414   a.kind != PObjectType::ATTRIBUTE_KIND_REFERENCE && obj_type?(a.type)
415 end
create_class(obj) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
56 def create_class(obj)
57   @dynamic_classes ||= Hash.new do |hash, key|
58     cls = key.implementation_class(false)
59     if cls.nil?
60       rp = key.resolved_parent
61       parent_class = rp.is_a?(PObjectType) ? rp.implementation_class : Object
62       class_def = ''
63       class_body(key, EMPTY_ARRAY, class_def)
64       cls = Class.new(parent_class)
65       cls.class_eval(class_def)
66       cls.define_singleton_method(:_pcore_type) { return key }
67       key.implementation_class = cls
68     end
69     hash[key] = cls
70   end
71   raise ArgumentError, "Expected a Puppet Type, got '#{obj.class.name}'" unless obj.is_a?(PAnyType)
72   @dynamic_classes[obj]
73 end
default_string(bld, a) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
449 def default_string(bld, a)
450   case a.value
451   when nil, true, false, Numeric, String
452     bld << a.value.inspect
453   else
454     bld << "_pcore_type['" << a.name << "'].value"
455   end
456 end
end_module(common_prefix, aliases, class_names, bld) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
135 def end_module(common_prefix, aliases, class_names, bld)
136   # Emit registration of contained type aliases
137   unless aliases.empty?
138     bld << "Puppet::Pops::Pcore.register_aliases({\n"
139     aliases.each { |name, type| bld << "  '" << name << "' => " << TypeFormatter.string(type.to_s) << "\n" }
140     bld.chomp!(",\n")
141     bld << "})\n\n"
142   end
143 
144   # Emit registration of contained types
145   unless class_names.empty?
146     bld << "Puppet::Pops::Pcore.register_implementations([\n"
147     class_names.each { |class_name| bld << '  ' << class_name << ",\n" }
148     bld.chomp!(",\n")
149     bld << "])\n\n"
150   end
151   bld.chomp!("\n")
152 
153   common_prefix.size.times { bld << "end\n" }
154 end
equals_default_string(bld, a) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
458 def equals_default_string(bld, a)
459   case a.value
460   when nil, true, false, Numeric, String
461     bld << '@' << a.name << ' == ' << a.value.inspect
462   else
463     bld << "_pcore_type['" << a.name << "'].default_value?(@" << a.name << ')'
464   end
465 end
implementation_names(object_types) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
156 def implementation_names(object_types)
157   object_types.map do |type|
158     ir = Loaders.implementation_registry
159     impl_name = ir.module_name_for_type(type)
160     raise Puppet::Error, "Unable to create an instance of #{type.name}. No mapping exists to runtime object" if impl_name.nil?
161     impl_name
162   end
163 end
module_definition(types, comment, *impl_subst) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
 82 def module_definition(types, comment, *impl_subst)
 83   object_types, aliased_types = types.partition { |type| type.is_a?(PObjectType) }
 84   if impl_subst.empty?
 85     impl_names = implementation_names(object_types)
 86   else
 87     impl_names = object_types.map { |type| type.name.gsub(*impl_subst) }
 88   end
 89 
 90   # extract common implementation module prefix
 91   names_by_prefix = Hash.new { |hash, key| hash[key] = [] }
 92   index = 0
 93   min_prefix_length = impl_names.reduce(Float::INFINITY) do |len, impl_name|
 94     segments = impl_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR)
 95     leaf_name = segments.pop
 96     names_by_prefix[segments.freeze] << [index, leaf_name, impl_name]
 97     index += 1
 98     len > segments.size ? segments.size : len
 99   end
100   min_prefix_length = 0 if min_prefix_length == Float::INFINITY
101 
102   common_prefix = []
103   segments_array = names_by_prefix.keys
104   min_prefix_length.times do |idx|
105     segment = segments_array[0][idx]
106     break unless segments_array.all? { |sn| sn[idx] == segment }
107     common_prefix << segment
108   end
109 
110   # Create class definition of all contained types
111   bld = ''
112   start_module(common_prefix, comment, bld)
113   class_names = []
114   names_by_prefix.each_pair do |seg_array, index_and_name_array|
115     added_to_common_prefix = seg_array[common_prefix.length..-1]
116     added_to_common_prefix.each { |name| bld << 'module ' << name << "\n" }
117     index_and_name_array.each do |idx, name, full_name|
118       scoped_class_definition(object_types[idx], name, bld, full_name, *impl_subst)
119       class_names << (added_to_common_prefix + [name]).join(TypeFormatter::NAME_SEGMENT_SEPARATOR)
120       bld << "\n"
121     end
122     added_to_common_prefix.size.times { bld << "end\n" }
123   end
124 
125   aliases = Hash[aliased_types.map { |type| [type.name, type.resolved_type] }]
126   end_module(common_prefix, aliases, class_names, bld)
127   bld
128 end
module_definition_from_typeset(typeset, *impl_subst) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
75 def module_definition_from_typeset(typeset, *impl_subst)
76   module_definition(
77     typeset.types.values,
78     "# Generated by #{self.class.name} from TypeSet #{typeset.name} on #{Date.new}\n",
79     *impl_subst)
80 end
namespace_relative(namespace_segments, name) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
52 def namespace_relative(namespace_segments, name)
53   remove_common_namespace(namespace_segments, name).join(TypeFormatter::NAME_SEGMENT_SEPARATOR)
54 end
obj_type?(t) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
417 def obj_type?(t)
418   case t
419   when PObjectType
420     true
421   when POptionalType
422     obj_type?(t.optional_type)
423   when PNotUndefType
424     obj_type?(t.type)
425   when PArrayType
426     obj_type?(t.element_type)
427   when PVariantType
428     t.types.all? { |v| obj_type?(v) }
429   else
430     false
431   end
432 end
remove_common_namespace(namespace_segments, name) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
43 def remove_common_namespace(namespace_segments, name)
44   segments = name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR)
45   namespace_segments.size.times do |idx|
46     break if segments.empty? || namespace_segments[idx] != segments[0]
47     segments.shift
48   end
49   segments
50 end
rname(name) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
467 def rname(name)
468   RUBY_RESERVED_WORDS[name] || name
469 end
scoped_class_definition(obj, leaf_name, bld, class_name, *impl_subst) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
175 def scoped_class_definition(obj, leaf_name, bld, class_name, *impl_subst)
176   bld << 'class ' << leaf_name
177   segments = class_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR)
178 
179   unless obj.parent.nil?
180     if impl_subst.empty?
181       ir = Loaders.implementation_registry
182       parent_name = ir.module_name_for_type(obj.parent)
183       raise Puppet::Error, "Unable to create an instance of #{obj.parent.name}. No mapping exists to runtime object" if parent_name.nil?
184     else
185       parent_name = obj.parent.name.gsub(*impl_subst)
186     end
187     bld << ' < ' << namespace_relative(segments, parent_name)
188   end
189 
190   bld << "\n"
191   bld << "  def self._pcore_type\n"
192   bld << '    @_pcore_type ||= ' << namespace_relative(segments, obj.class.name) << ".new('" << obj.name << "', "
193   bld << TypeFormatter.singleton.ruby('ref').indented(2).string(obj._pcore_init_hash(false)) << ")\n"
194   bld << "  end\n"
195 
196   class_body(obj, segments, bld)
197 
198   bld << "end\n"
199 end
start_module(common_prefix, comment, bld) click to toggle source
    # File lib/puppet/pops/types/ruby_generator.rb
130 def start_module(common_prefix, comment, bld)
131   bld << '# ' << comment << "\n"
132   common_prefix.each { |cp| bld << 'module ' << cp << "\n" }
133 end