1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
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<Class>] :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<Class>] :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 is_constant_defined?(const)
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
# @api private
#
def is_constant_defined?(const)
const_defined?(const, false)
end
# Handle the setting and/or removing of the associated constant.
# @api private
#
def handleclassconst(klass, name, options)
const = genconst_string(name, options)
if is_constant_defined?(const)
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
|