Class: Caesars
- Inherits:
-
Object
- Object
- Caesars
- Defined in:
- lib/caesars.rb,
lib/caesars/hash.rb,
lib/caesars/config.rb,
lib/caesars/exceptions.rb
Overview
A helper for loading a DSL from a config file.
Usage:
class Staff < Caesars; end;
class StaffConfig < Caesars::Config
dsl Staff::DSL
end
@config = StaffConfig.new('/path/2/staff_dsl.rb')
p @config.staff # => <Staff:0x7ea450 ... >
Defined Under Namespace
Classes: Config, Error, Hash, OrderedHash, SyntaxError
Constant Summary collapse
- VERSION =
"0.7.4"
- HASH_TYPE =
(RUBY_VERSION =~ /1.9/) ? ::Hash : Caesars::OrderedHash
- DIGEST_TYPE =
Digest::SHA1
- @@debug =
false
- @@chilled =
{}
- @@forced_array =
{}
- @@forced_ignore =
{}
- @@known_symbols =
[]
- @@known_symbols_by_glass =
{}
Instance Attribute Summary collapse
-
#caesars_properties ⇒ Object
An instance of Caesars::Hash which contains the data specified by your DSL.
Class Method Summary collapse
-
.add_known_symbol(g, s) ⇒ Object
Add
s
to the list of global symbols (across all instances of Caesars). -
.chill(caesars_meth) ⇒ Object
Specify a method that should delay execution of its block.
-
.chilled?(name) ⇒ Boolean
Is the given
name
chilled? See Caesars.chill. - .debug? ⇒ Boolean
- .disable_debug ⇒ Object
- .enable_debug ⇒ Object
-
.forced_array(caesars_meth) ⇒ Object
Specify a method that should store it’s args as nested Arrays Here’s an example:.
-
.forced_array?(name) ⇒ Boolean
Is the given
name
a forced array? See Caesars.forced_array. -
.forced_hash(caesars_meth, &b) ⇒ Object
Force the specified keyword to always be treated as a hash.
-
.forced_ignore(caesars_meth) ⇒ Object
Specify a method that should always be ignored.
-
.forced_ignore?(name) ⇒ Boolean
Is the given
name
a forced ignore? See Caesars.forced_ignore. -
.glass(klass) ⇒ Object
Returns the lowercase name of
klass
. -
.inherited(modname) ⇒ Object
Executes automatically when Caesars is subclassed.
-
.known_symbol?(s) ⇒ Boolean
Is
s
in the global keyword list? (accross all instances of Caesars). -
.known_symbol_by_glass?(g, s) ⇒ Boolean
Is
s
in the keyword list for glassg
?.
Instance Method Summary collapse
-
#[](name) ⇒ Object
Act a bit like a hash for the case: @subclass.
-
#[]=(name, value) ⇒ Object
Act a bit like a hash for the case: @subclass = value.
-
#add_known_symbol(s) ⇒ Object
Add
keyword
to the list of known symbols for this instances as well as to the master known symbols list. -
#find(*criteria) ⇒ Object
Looks for the specific attribute specified.
-
#find_deferred(*criteria) ⇒ Object
Look for an attribute, bubbling up through the parents until it’s found.
-
#find_deferred_old(*criteria) ⇒ Object
DEPRECATED – use find_deferred.
-
#glass ⇒ Object
Returns the lowercase name of the class.
-
#initialize(name = nil) ⇒ Caesars
constructor
A new instance of Caesars.
-
#keys ⇒ Object
Returns an array of the available top-level attributes.
-
#known_symbol?(s) ⇒ Boolean
Has
s
already appeared as a keyword in the DSL for this glass type?. -
#method_missing(meth, *args, &b) ⇒ Object
This method handles all of the attributes that are not forced hashes It’s used in the DSL for handling attributes dyanamically (that weren’t defined previously) and also in subclasses of Caesars for returning the appropriate attribute values.
-
#to_hash ⇒ Object
Returns the parsed tree as a regular hash (instead of a Caesars::Hash).
Constructor Details
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args, &b) ⇒ Object
This method handles all of the attributes that are not forced hashes It’s used in the DSL for handling attributes dyanamically (that weren’t defined previously) and also in subclasses of Caesars for returning the appropriate attribute values.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/caesars.rb', line 254 def method_missing(meth, *args, &b) STDERR.puts "Caesars.method_missing: #{meth}" if Caesars.debug? add_known_symbol(meth) if Caesars.forced_ignore?(meth) STDERR.puts "Forced ignore: #{meth}" if Caesars.debug? return end # Handle the setter, attribute= if meth.to_s =~ /=$/ && @caesars_properties.has_key?(meth.to_s.chop.to_sym) return @caesars_properties[meth.to_s.chop.to_sym] = (args.size == 1) ? args.first : args end return @caesars_properties[meth] if @caesars_properties.has_key?(meth) && args.empty? && b.nil? # We there are no args and no block, we return nil. This is useful # for calls to methods on a Caesars::Hash object that don't have a # value (so we cam treat self[:someval] the same as self.someval). if args.empty? && b.nil? # We make an exception for methods that we are already expecting. if Caesars.forced_array?(meth) return @caesars_pointer[meth] ||= Caesars::Hash.new else return nil end end if b if Caesars.forced_array?(meth) @caesars_pointer[meth] ||= [] args << b # Forced array blocks are always chilled and at the end @caesars_pointer[meth] << args else # We loop through each of the arguments sent to "meth". # Elements are added for each of the arguments and the # contents of the block will be applied to each one. # This is an important feature for Rudy configs since # it allows defining several environments, roles, etc # at the same time. # env :dev, :stage, :prod do # ... # end # Use the name of the method if no name is supplied. args << meth if args.empty? args.each do |name| prev = @caesars_pointer @caesars_pointer[name] ||= Caesars::Hash.new if Caesars.chilled?(meth) @caesars_pointer[name] = b else @caesars_pointer = @caesars_pointer[name] begin b.call if b rescue ArgumentError, SyntaxError => ex STDERR.puts "CAESARS: error in #{meth} (#{args.join(', ')})" raise ex end @caesars_pointer = prev end end end # We've seen this attribute before, add the value to the existing element elsif @caesars_pointer.kind_of?(Hash) && @caesars_pointer[meth] if Caesars.forced_array?(meth) @caesars_pointer[meth] ||= [] @caesars_pointer[meth] << args else # Make the element an Array once there's more than a single value unless @caesars_pointer[meth].is_a?(Array) @caesars_pointer[meth] = [@caesars_pointer[meth]] end @caesars_pointer[meth] += args end elsif !args.empty? if Caesars.forced_array?(meth) @caesars_pointer[meth] = [args] else @caesars_pointer[meth] = args.size == 1 ? args.first : args end end end |
Instance Attribute Details
#caesars_properties ⇒ Object
An instance of Caesars::Hash which contains the data specified by your DSL
71 72 73 |
# File 'lib/caesars.rb', line 71 def caesars_properties @caesars_properties end |
Class Method Details
.add_known_symbol(g, s) ⇒ Object
Add s
to the list of global symbols (across all instances of Caesars)
53 54 55 56 57 58 59 |
# File 'lib/caesars.rb', line 53 def Caesars.add_known_symbol(g, s) g = Caesars.glass(g) STDERR.puts "add_symbol: #{g} => #{s}" if Caesars.debug? @@known_symbols << s.to_sym @@known_symbols_by_glass[g] ||= [] @@known_symbols_by_glass[g] << s.to_sym end |
.chill(caesars_meth) ⇒ Object
Specify a method that should delay execution of its block. Here’s an example:
class Food < Caesars
chill :count
end
food do
taste :delicious
count do |items|
puts items + 2
end
end
@food.count.call(3) # => 5
438 439 440 441 442 443 |
# File 'lib/caesars.rb', line 438 def self.chill(caesars_meth) STDERR.puts "chill: #{caesars_meth}" if Caesars.debug? Caesars.add_known_symbol(self, caesars_meth) @@chilled[caesars_meth.to_sym] = true nil end |
.chilled?(name) ⇒ Boolean
Is the given name
chilled? See Caesars.chill
35 36 37 38 |
# File 'lib/caesars.rb', line 35 def Caesars.chilled?(name) return false unless name @@chilled.has_key?(name.to_sym) end |
.disable_debug ⇒ Object
31 |
# File 'lib/caesars.rb', line 31 def Caesars.disable_debug; @@debug = false; end |
.enable_debug ⇒ Object
30 |
# File 'lib/caesars.rb', line 30 def Caesars.enable_debug; @@debug = true; end |
.forced_array(caesars_meth) ⇒ Object
Specify a method that should store it’s args as nested Arrays Here’s an example:
class Food < Caesars
forced_array :sauce
end
food do
taste :delicious
sauce :tabasco, :worcester
sauce :franks
end
@food.sauce # => [[:tabasco, :worcester], [:franks]]
The blocks for elements that are specified as forced_array will be chilled (stored as Proc objects). The block is put at the end of the Array. e.g.
food do
sauce :arg1, :arg2 do
...
end
end
@food.sauce # => [[:inline_method, :arg1, :arg2, #<Proc:0x1fa552>]]
472 473 474 475 476 477 |
# File 'lib/caesars.rb', line 472 def self.forced_array(caesars_meth) STDERR.puts "forced_array: #{caesars_meth}" if Caesars.debug? Caesars.add_known_symbol(self, caesars_meth) @@forced_array[caesars_meth.to_sym] = true nil end |
.forced_array?(name) ⇒ Boolean
Is the given name
a forced array? See Caesars.forced_array
41 42 43 44 |
# File 'lib/caesars.rb', line 41 def Caesars.forced_array?(name) return false unless name @@forced_array.has_key?(name.to_sym) end |
.forced_hash(caesars_meth, &b) ⇒ Object
Force the specified keyword to always be treated as a hash. Example:
startup do
disks do
create "/path/2" # Available as hash: [action][disks][create][/path/2] == {}
create "/path/4" do # Available as hash: [action][disks][create][/path/4] == {size => 14}
size 14
end
end
end
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/caesars.rb', line 356 def self.forced_hash(caesars_meth, &b) STDERR.puts "forced_hash: #{caesars_meth}" if Caesars.debug? Caesars.add_known_symbol(self, caesars_meth) module_eval %Q{ def #{caesars_meth}(*caesars_names,&b) this_meth = :'#{caesars_meth}' add_known_symbol(this_meth) if Caesars.forced_ignore?(this_meth) STDERR.puts "Forced ignore: \#{this_meth}" if Caesars.debug? return end if @caesars_properties.has_key?(this_meth) && caesars_names.empty? && b.nil? return @caesars_properties[this_meth] end return nil if caesars_names.empty? && b.nil? return method_missing(this_meth, *caesars_names, &b) if caesars_names.empty? # Take the first argument in the list provided to "caesars_meth" caesars_name = caesars_names.shift prev = @caesars_pointer @caesars_pointer[this_meth] ||= Caesars::Hash.new hash = Caesars::Hash.new if @caesars_pointer[this_meth].has_key?(caesars_name) STDERR.puts "duplicate key ignored: \#{caesars_name}" return end # The pointer is pointing to the hash that contains "this_meth". # We wan't to make it point to the this_meth hash so when we call # the block, we'll create new entries in there. @caesars_pointer = hash if Caesars.chilled?(this_meth) # We're done processing this_meth so we want to return the pointer # to the level above. @caesars_pointer = prev @caesars_pointer[this_meth][caesars_name] = b else if b # Since the pointer is pointing to the this_meth hash, all keys # created in the block we be placed inside. b.call end # We're done processing this_meth so we want to return the pointer # to the level above. @caesars_pointer = prev @caesars_pointer[this_meth][caesars_name] = hash end # All other arguments provided to "caesars_meth" # will reference the value for the first argument. caesars_names.each do |name| @caesars_pointer[this_meth][name] = @caesars_pointer[this_meth][caesars_name] end @caesars_pointer = prev # Make sure we're back up one level end } nil end |
.forced_ignore(caesars_meth) ⇒ Object
Specify a method that should always be ignored. Here’s an example:
class Food < Caesars
forced_ignore :taste
end
food do
taste :delicious
end
@food.taste # => nil
492 493 494 495 496 497 |
# File 'lib/caesars.rb', line 492 def self.forced_ignore(caesars_meth) STDERR.puts "forced_ignore: #{caesars_meth}" if Caesars.debug? Caesars.add_known_symbol(self, caesars_meth) @@forced_ignore[caesars_meth.to_sym] = true nil end |
.forced_ignore?(name) ⇒ Boolean
Is the given name
a forced ignore? See Caesars.forced_ignore
47 48 49 50 |
# File 'lib/caesars.rb', line 47 def Caesars.forced_ignore?(name) return false unless name @@forced_ignore.has_key?(name.to_sym) end |
.glass(klass) ⇒ Object
Returns the lowercase name of klass
. i.e. Some::Taste # => taste
248 |
# File 'lib/caesars.rb', line 248 def self.glass(klass); (klass.to_s.split(/::/)).last.downcase.to_sym; end |
.inherited(modname) ⇒ Object
Executes automatically when Caesars is subclassed. This creates the YourClass::DSL module which contains a single method named after YourClass that is used to catch the top level DSL method.
For example, if your class is called Glasses::HighBall, your top level method would be: highball.
highball :mine do
volume "9oz"
end
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
# File 'lib/caesars.rb', line 510 def self.inherited(modname) STDERR.puts "INHERITED: #{modname}" if Caesars.debug? # NOTE: We may be able to replace this without an eval using Module.nesting meth = (modname.to_s.split(/::/)).last.downcase # Some::HighBall => highball # The method name "meth" is now a known symbol # for the short class name (also "meth"). Caesars.add_known_symbol(meth, meth) # We execute a module_eval form the namespace of the inherited class # so when we define the new module DSL it will be Some::HighBall::DSL. modname.module_eval %Q{ module DSL def #{meth}(*args, &b) name = !args.empty? ? args.first.to_s : nil varname = "@#{meth.to_s}" varname << "_\#{name}" if name inst = instance_variable_get(varname) # When the top level DSL method is called without a block # it will return the appropriate instance variable name return inst if b.nil? # Add to existing instance, if it exists. Otherwise create one anew. # NOTE: Module.nesting[1] == modname (e.g. Some::HighBall) inst = instance_variable_set(varname, inst || Module.nesting[1].new(name)) inst.instance_eval(&b) inst end def self.methname :"#{meth}" end end }, __FILE__, __LINE__ end |
.known_symbol?(s) ⇒ Boolean
Is s
in the global keyword list? (accross all instances of Caesars)
62 |
# File 'lib/caesars.rb', line 62 def Caesars.known_symbol?(s); @@known_symbols.member?(s.to_sym); end |
.known_symbol_by_glass?(g, s) ⇒ Boolean
Is s
in the keyword list for glass g
?
64 65 66 67 68 |
# File 'lib/caesars.rb', line 64 def Caesars.known_symbol_by_glass?(g, s) g &&= g.to_sym @@known_symbols_by_glass[g] ||= [] @@known_symbols_by_glass[g].member?(s.to_sym) end |
Instance Method Details
#[](name) ⇒ Object
Act a bit like a hash for the case: @subclass
220 221 222 223 |
# File 'lib/caesars.rb', line 220 def [](name) return @caesars_properties[name] if @caesars_properties.has_key?(name) return @caesars_properties[name.to_sym] if @caesars_properties.has_key?(name.to_sym) end |
#[]=(name, value) ⇒ Object
Act a bit like a hash for the case: @subclass = value
227 228 229 |
# File 'lib/caesars.rb', line 227 def []=(name, value) @caesars_properties[name] = value end |
#add_known_symbol(s) ⇒ Object
Add keyword
to the list of known symbols for this instances as well as to the master known symbols list. See: known_symbol?
233 234 235 236 237 |
# File 'lib/caesars.rb', line 233 def add_known_symbol(s) @@known_symbols << s.to_sym @@known_symbols_by_glass[glass] ||= [] @@known_symbols_by_glass[glass] << s.to_sym end |
#find(*criteria) ⇒ Object
Looks for the specific attribute specified. criteria
is an array of attribute names, orders according to their relationship. The last element is considered to the desired attribute. It can be an array.
Unlike find_deferred, it will return only the value specified, otherwise nil.
208 209 210 211 212 213 214 215 216 |
# File 'lib/caesars.rb', line 208 def find(*criteria) criteria.flatten! if criteria.first.is_a?(Array) p criteria if Caesars.debug? # BUG: Attributes can be stored as strings and here we only look for symbols str = criteria.collect { |v| "[:'#{v}']" if v }.join eval_str = "@caesars_properties#{str} if defined?(@caesars_properties#{str})" val = eval eval_str val end |
#find_deferred(*criteria) ⇒ Object
Look for an attribute, bubbling up through the parents until it’s found. criteria
is an array of hierarchical attributes, ordered according to their relationship. The last element is the desired attribute to find. Looking for ‘ami’:
find_deferred(:environment, :role, :ami)
First checks at @caesars_properties[:role] Then, @caesars_properties[:ami] Finally, @caesars_properties
If the last element is an Array, it’s assumed that only that combination should be returned.
find_deferred(:environment, :role:, [:disks, '/file/path'])
Search order:
- :environment][:disks][‘/file/path’
- :environment][‘/file/path’
- :disks][‘/file/path’
Other nested Arrays are treated special too. We look at the criteria from right to left and remove the first nested element we find.
find_deferred([:region, :zone], :environment, :role, :ami)
Search order:
- :region][:environment][:ami
- :region][:role][:ami
- :environment][:ami
- :environment][:ami
- :ami
NOTE: There is a maximum depth of 10.
Returns the attribute if found or nil.
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 |
# File 'lib/caesars.rb', line 154 def find_deferred(*criteria) # The last element is assumed to be the attribute we're looking for. # The purpose of this function is to bubble up the hierarchy of a # hash to find it. att = criteria.pop # Account for everything being sent as an Array # i.e. find([1, 2, :attribute]) # We don't use flatten b/c we don't want to disturb nested Arrays if criteria.empty? criteria = att att = criteria.pop end found = nil sacrifice = nil while !criteria.empty? found = find(criteria, att) break if found # Nested Arrays are treated special. We look at the criteria from # right to left and remove the first nested element we find. # # i.e. [['a', 'b'], 1, 2, [:first, :second], :attribute] # # In this example, :second will be removed first. criteria.reverse.each_with_index do |el,index| next unless el.is_a?(Array) # Ignore regular criteria next if el.empty? # Ignore empty nested hashes sacrifice = el.pop break end # Remove empty nested Arrays criteria.delete_if { |el| el.is_a?(Array) && el.empty? } # We need to make a sacrifice sacrifice = criteria.pop if sacrifice.nil? break if (limit ||= 0) > 10 # A failsafe limit += 1 sacrifice = nil end found || find(att) # One last try in the root namespace end |
#find_deferred_old(*criteria) ⇒ Object
DEPRECATED – use find_deferred
Look for an attribute, bubbling up to the parent if it’s not found criteria
is an array of attribute names, orders according to their relationship. The last element is considered to the desired attribute. It can be an array.
# Looking for 'attribute'.
# First checks at @caesars_properties[grandparent][parent][attribute]
# Then, @caesars_properties[grandparent][attribute]
# Finally, @caesars_properties[attribute]
find_deferred('grandparent', 'parent', 'attribute')
Returns the attribute if found or nil.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/caesars.rb', line 101 def find_deferred_old(*criteria) # This is a nasty implementation. Sorry me! I'll enjoy a few # caesars and be right with you. att = criteria.pop val = nil while !criteria.empty? p [criteria, att].flatten if Caesars.debug? val = find(criteria, att) break if val criteria.pop end # One last try in the root namespace val = @caesars_properties[att.to_sym] if defined?(@caesars_properties[att.to_sym]) && !val val end |
#glass ⇒ Object
Returns the lowercase name of the class. i.e. Some::Taste # => taste
245 |
# File 'lib/caesars.rb', line 245 def glass; @glass ||= (self.class.to_s.split(/::/)).last.downcase.to_sym; end |
#keys ⇒ Object
Returns an array of the available top-level attributes
81 |
# File 'lib/caesars.rb', line 81 def keys; @caesars_properties.keys; end |
#known_symbol?(s) ⇒ Boolean
Has s
already appeared as a keyword in the DSL for this glass type?
240 241 242 |
# File 'lib/caesars.rb', line 240 def known_symbol?(s) @@known_symbols_by_glass[glass] && @@known_symbols_by_glass[glass].member?(s) end |
#to_hash ⇒ Object
Returns the parsed tree as a regular hash (instead of a Caesars::Hash)
84 |
# File 'lib/caesars.rb', line 84 def to_hash; @caesars_properties.to_hash; end |