Parent

Caesars

Caesars -- Rapid DSL prototyping in Ruby.

Subclass Caesars and start drinking! I mean, start prototyping your own domain specific language!

See bin/example


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 ... >

Attributes

caesars_properties[RW]

An instance of Caesars::Hash which contains the data specified by your DSL

Public Class Methods

add_known_symbol(g, s) click to toggle source

Add s to the list of global symbols (across all instances of Caesars)

# 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) click to toggle source

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
# 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) click to toggle source

Is the given name chilled? See Caesars.chill

# File lib/caesars.rb, line 35
def Caesars.chilled?(name)
  return false unless name
  @@chilled.has_key?(name.to_sym)
end
debug?() click to toggle source
# File lib/caesars.rb, line 32
def Caesars.debug?; @@debug; end
disable_debug() click to toggle source
# File lib/caesars.rb, line 31
def Caesars.disable_debug; @@debug = false; end
enable_debug() click to toggle source
# File lib/caesars.rb, line 30
def Caesars.enable_debug; @@debug = true; end
forced_array(caesars_meth) click to toggle source

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>]]
# 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) click to toggle source

Is the given name a forced array? See Caesars.forced_array

# 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) click to toggle source

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
# 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 %{
    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) click to toggle source

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
# 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) click to toggle source

Is the given name a forced ignore? See Caesars.forced_ignore

# 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) click to toggle source

Returns the lowercase name of klass. i.e. Some::Taste # => taste

# File lib/caesars.rb, line 248
def self.glass(klass); (klass.to_s.split(/::/)).last.downcase.to_sym; end
inherited(modname) click to toggle source

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
# 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 %{
    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) click to toggle source

Is s in the global keyword list? (accross all instances of Caesars)

# File lib/caesars.rb, line 62
def Caesars.known_symbol?(s); @@known_symbols.member?(s.to_sym); end
known_symbol_by_glass?(g, s) click to toggle source

Is s in the keyword list for glass g?

# 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
new(name=nil) click to toggle source
# File lib/caesars.rb, line 73
def initialize(name=nil)
  @caesars_name = name if name
  @caesars_properties = Caesars::Hash.new
  @caesars_pointer = @caesars_properties
  init if respond_to?(:init)
end

Public Instance Methods

[](name) click to toggle source

Act a bit like a hash for the case: @subclass

# 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) click to toggle source

Act a bit like a hash for the case: @subclass = value

# File lib/caesars.rb, line 227
def []=(name, value)
  @caesars_properties[name] = value
end
add_known_symbol(s) click to toggle source

Add keyword to the list of known symbols for this instances as well as to the master known symbols list. See: known_symbol?

# 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) click to toggle source

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.

# 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) click to toggle source

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:

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:

NOTE: There is a maximum depth of 10.

Returns the attribute if found or nil.

# 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) click to toggle source

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.

# 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() click to toggle source

Returns the lowercase name of the class. i.e. Some::Taste # => taste

# File lib/caesars.rb, line 245
def glass; @glass ||= (self.class.to_s.split(/::/)).last.downcase.to_sym; end
keys() click to toggle source

Returns an array of the available top-level attributes

# File lib/caesars.rb, line 81
def keys; @caesars_properties.keys; end
known_symbol?(s) click to toggle source

Has s already appeared as a keyword in the DSL for this glass type?

# File lib/caesars.rb, line 240
def known_symbol?(s)
  @@known_symbols_by_glass[glass] && @@known_symbols_by_glass[glass].member?(s)
end
method_missing(meth, *args, &b) click to toggle source

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.

# 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
to_hash() click to toggle source

Returns the parsed tree as a regular hash (instead of a Caesars::Hash)

# File lib/caesars.rb, line 84
def to_hash; @caesars_properties.to_hash; end

[Validate]

Generated with the Darkfish Rdoc Generator 2.