module Puppet class ConstantAlreadyDefined < Error; end class SubclassAlreadyDefined < Error; end end module Puppet::Util::ClassGen include Puppet::Util::MethodHelper include Puppet::Util # Create a new subclass. Valid options are: # * :array: An array of existing classes. If specified, the new # class is added to this array. # * :attributes: A hash of attributes to set before the block is # evaluated. # * :block: The block to evaluate in the context of the class. # You can also just pass the block normally, but it will still be evaluated # with class_eval. # * :constant: What to set the constant as. Defaults to the # capitalized name. # * :hash: A hash of existing classes. If specified, the new # class is added to this hash, and it is also used for overwrite tests. # * :overwrite: Whether to overwrite an existing class. # * :parent: The parent class for the generated class. Defaults to # self. # * :prefix: The constant prefix. Default to nothing; if specified, # the capitalized name is appended and the result is set as the constant. def genclass(name, options = {}, &block) genthing(name, Class, options, block) end # Create a new module. Valid options are: # * :array: An array of existing classes. If specified, the new # class is added to this array. # * :attributes: A hash of attributes to set before the block is # evaluated. # * :block: The block to evaluate in the context of the class. # You can also just pass the block normally, but it will still be evaluated # with class_eval. # * :constant: What to set the constant as. Defaults to the # capitalized name. # * :hash: A hash of existing classes. If specified, the new # class is added to this hash, and it is also used for overwrite tests. # * :overwrite: Whether to overwrite an existing class. # * :prefix: The constant prefix. Default to nothing; if specified, # the capitalized name is appended and the result is set as the constant. def genmodule(name, options = {}, &block) genthing(name, Module, options, block) end # Remove an existing class def rmclass(name, options) options = symbolize_options(options) const = genconst_string(name, options) retval = false if const_defined?(const) remove_const(const) retval = true end if hash = options[:hash] and hash.include? name hash.delete(name) retval = true end # Let them know whether we did actually delete a subclass. retval end private # Generate the constant to create or remove. def genconst_string(name, options) unless const = options[:constant] 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. def genthing(name, type, options, block) options = symbolize_options(options) name = symbolize(name.to_s.downcase) 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 # const_defined? in Ruby 1.9 behaves differently in terms # of which class hierarchy it polls for nested namespaces # # See http://redmine.ruby-lang.org/issues/show/1915 def is_constant_defined?(const) if ::RUBY_VERSION =~ /1.9/ const_defined?(const, false) else const_defined?(const) end end # Handle the setting and/or removing of the associated constant. def handleclassconst(klass, name, options) const = genconst_string(name, options) if is_constant_defined?(const) if options[:overwrite] Puppet.info "Redefining #{name} in #{self}" remove_const(const) else raise Puppet::ConstantAlreadyDefined, "Class #{const} is already defined in #{self}" end end const_set(const, klass) const end # Perform the initializations on the class. def initclass(klass, options) klass.initvars if klass.respond_to? :initvars if attrs = options[:attributes] 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| if set = options[method] 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. def name2const(name) name.to_s.capitalize end # Store the class in the appropriate places. def storeclass(klass, klassname, options) if hash = options[:hash] if hash.include? klassname and ! options[:overwrite] raise Puppet::SubclassAlreadyDefined, "Already a generated class named #{klassname}" end hash[klassname] = klass end # If we were told to stick it in a hash, then do so if array = options[: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}" end array << klass end end end