Class: Caesars
- Inherits:
-
Object
- Object
- Caesars
- Defined in:
- lib/caesars.rb
Overview
Caesars – Rapid DSL prototyping in Ruby.
Subclass Caesars and start drinking! I mean, start prototyping your own domain specific language!
See bin/example
Defined Under Namespace
Classes: Config, Error, Hash, OrderedHash, SyntaxError
Constant Summary collapse
- VERSION =
"0.7.0"
- HASH_TYPE =
(RUBY_VERSION =~ /1.9/) ? ::Hash : Caesars::OrderedHash
- @@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.
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 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/caesars.rb', line 289 def method_missing(meth, *args, &b) 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
92 93 94 |
# File 'lib/caesars.rb', line 92 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)
45 46 47 48 49 50 51 |
# File 'lib/caesars.rb', line 45 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
464 465 466 467 468 469 |
# File 'lib/caesars.rb', line 464 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
27 28 29 30 |
# File 'lib/caesars.rb', line 27 def Caesars.chilled?(name) return false unless name @@chilled.has_key?(name.to_sym) end |
.disable_debug ⇒ Object
23 |
# File 'lib/caesars.rb', line 23 def Caesars.disable_debug; @@debug = false; end |
.enable_debug ⇒ Object
22 |
# File 'lib/caesars.rb', line 22 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>]]
498 499 500 501 502 503 |
# File 'lib/caesars.rb', line 498 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
33 34 35 36 |
# File 'lib/caesars.rb', line 33 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
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 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/caesars.rb', line 390 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? # TODO: This should be a loop 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 @caesars_pointer = prev 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
518 519 520 521 522 523 |
# File 'lib/caesars.rb', line 518 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
39 40 41 42 |
# File 'lib/caesars.rb', line 39 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
283 |
# File 'lib/caesars.rb', line 283 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
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 |
# File 'lib/caesars.rb', line 536 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)
54 |
# File 'lib/caesars.rb', line 54 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
?
56 57 58 59 60 |
# File 'lib/caesars.rb', line 56 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
255 256 257 258 |
# File 'lib/caesars.rb', line 255 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
262 263 264 |
# File 'lib/caesars.rb', line 262 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?
268 269 270 271 272 |
# File 'lib/caesars.rb', line 268 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.
243 244 245 246 247 248 249 250 251 |
# File 'lib/caesars.rb', line 243 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.
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 229 230 231 232 233 234 235 |
# File 'lib/caesars.rb', line 189 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.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/caesars.rb', line 136 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
280 |
# File 'lib/caesars.rb', line 280 def glass; @glass ||= (self.class.to_s.split(/::/)).last.downcase.to_sym; end |
#keys ⇒ Object
Returns an array of the available top-level attributes
116 |
# File 'lib/caesars.rb', line 116 def keys; @caesars_properties.keys; end |
#known_symbol?(s) ⇒ Boolean
Has s
already appeared as a keyword in the DSL for this glass type?
275 276 277 |
# File 'lib/caesars.rb', line 275 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)
119 |
# File 'lib/caesars.rb', line 119 def to_hash; @caesars_properties.to_hash; end |