# frozen_string_literal: true module Puppet class ConstantAlreadyDefined < Error; end class SubclassAlreadyDefined < Error; end end # This is a utility module for generating classes. # @api public # module Puppet::Util::ClassGen include Puppet::Util # Create a new class. # @param name [String] the name of the generated class # @param options [Hash] a hash of options # @option options [Array] :array if specified, the generated class is appended to this array # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided # this way, or as a normal yield block). # @option options [String] :constant (name with first letter capitalized) what to set the constant that references # the generated class to. # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). # This hash must be specified if the `:overwrite` option is set to `true`. # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also # defining the `:hash` with existing classes as the test is based on the content of this hash). # @option options [Class] :parent (self) the parent class of the generated class. # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the # generated class. # @return [Class] the generated class # def genclass(name, options = {}, &block) genthing(name, Class, options, block) end # Creates a new module. # @param name [String] the name of the generated module # @param options [Hash] hash with options # @option options [Array] :array if specified, the generated class is appended to this array # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided # this way, or as a normal yield block). # @option options [String] :constant (name with first letter capitalized) what to set the constant that references # the generated class to. # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). # This hash must be specified if the `:overwrite` option is set to `true`. # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also # defining the `:hash` with existing classes as the test is based on the content of this hash). # the capitalized name is appended and the result is set as the constant. # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the # generated class. # @return [Module] the generated Module def genmodule(name, options = {}, &block) genthing(name, Module, options, block) end # Removes an existing class. # @param name [String] the name of the class to remove # @param options [Hash] options # @option options [Hash] :hash a hash of existing classes from which the class to be removed is also removed # @return [Boolean] whether the class was removed or not # def rmclass(name, options) const = genconst_string(name, options) retval = false if const_defined?(const, false) remove_const(const) retval = true end hash = options[:hash] if hash && hash.include?(name) hash.delete(name) retval = true end # Let them know whether we did actually delete a subclass. retval end private # Generates the constant to create or remove. # @api private def genconst_string(name, options) const = options[:constant] unless const prefix = options[:prefix] || "" const = prefix + name2const(name) end const end # This does the actual work of creating our class or module. It's just a # slightly abstract version of genclass. # @api private def genthing(name, type, options, block) name = name.to_s.downcase.intern if type == Module # evalmethod = :module_eval evalmethod = :class_eval # Create the class, with the correct name. klass = Module.new do class << self attr_reader :name end @name = name end else options[:parent] ||= self evalmethod = :class_eval # Create the class, with the correct name. klass = Class.new(options[:parent]) do @name = name end end # Create the constant as appropriation. handleclassconst(klass, name, options) # Initialize any necessary variables. initclass(klass, options) block ||= options[:block] # Evaluate the passed block if there is one. This should usually # define all of the work. klass.send(evalmethod, &block) if block klass.postinit if klass.respond_to? :postinit # Store the class in hashes or arrays or whatever. storeclass(klass, name, options) klass end # Handle the setting and/or removing of the associated constant. # @api private # def handleclassconst(klass, name, options) const = genconst_string(name, options) if const_defined?(const, false) if options[:overwrite] Puppet.info _("Redefining %{name} in %{klass}") % { name: name, klass: self } remove_const(const) else raise Puppet::ConstantAlreadyDefined, _("Class %{const} is already defined in %{klass}") % { const: const, klass: self } end end const_set(const, klass) const end # Perform the initializations on the class. # @api private # def initclass(klass, options) klass.initvars if klass.respond_to? :initvars attrs = options[:attributes] if attrs attrs.each do |param, value| method = param.to_s + "=" klass.send(method, value) if klass.respond_to? method end end [:include, :extend].each do |method| set = options[method] if set set = [set] unless set.is_a?(Array) set.each do |mod| klass.send(method, mod) end end end klass.preinit if klass.respond_to? :preinit end # Convert our name to a constant. # @api private def name2const(name) name.to_s.capitalize end # Store the class in the appropriate places. # @api private def storeclass(klass, klassname, options) hash = options[:hash] if hash if hash.include? klassname and !options[:overwrite] raise Puppet::SubclassAlreadyDefined, _("Already a generated class named %{klassname}") % { klassname: klassname } end hash[klassname] = klass end # If we were told to stick it in a hash, then do so array = options[:array] if array if (klass.respond_to? :name and array.find { |c| c.name == klassname } and !options[:overwrite]) raise Puppet::SubclassAlreadyDefined, _("Already a generated class named %{klassname}") % { klassname: klassname } end array << klass end end end