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
36 def self.protect_reserved_name(name)
37   RUBY_RESERVED_WORDS[name] || name
38 end
unprotect_reserved_name(name) click to toggle source
   # File lib/puppet/pops/types/ruby_generator.rb
40 def self.unprotect_reserved_name(name)
41   RUBY_RESERVED_WORDS_REVERSED[name] || name
42 end

Public Instance Methods

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