class DslBlock

DslBlock is a base class for defining a Domain Specific Language. Subclasses of DslBlock define the desired dsl. These methods become available to ruby code running in the context of the subclass. The block execution is automatically isolated to prevent the called block from accessing instance methods unless specifically designated as callable. DslBlocks can be nested and parent blocks can allow their methods to be exposed to child block.

Example

# Define three DslBlocks each with at least one command in each block
class Foo < DslBlock
  commands :show_foo
  def show_foo(x)
    "Mr. T says you are a foo times #{x.to_i}"
  end
end

class Bar < DslBlock
  commands :show_bar
  def show_bar(x)
    "Ordering #{x.to_i} Shirley Temples from the bar"
  end
end

class Baz < DslBlock
  commands :show_baz
  def show_baz(x)
    "Baz spaz #{x.inspect}"
  end
end

# Connect the blocks to each other so they can be easily nested
Baz.add_command_to(Bar)
Bar.add_command_to(Foo, :propagate => true) # Let Bar blocks also respond to foo methods
Foo.add_command_to(self)

# Use the new DSL
foo do
  self.inspect       # => #<Foo:0x007fdbd52b54e0 @block=#<Proc:0x007fdbd52b5530@/home/fhall/wonderland/alice.rb:29>, @parent=nil>
  x = 10/10
  show_foo x         # => Mr. T says you are a foo times 1

  bar do
    x *= 2
    show_bar x       # => Ordering 2 Shirley Temples from the bar

    x += 1
    show_foo x       # => Mr. T says you are a foo times 3

    baz do

      x *= 4
      x /= 3
      show_baz x     # => Baz spaz 4

      begin
        x += 1
        show_bar x   # => NameError
      rescue NameError
        'No bar for us'
      end
    end

  end

end

Constants

VERSION

2.0.0

Attributes

block[RW]

Block of code that will be executed upon yield.

parent[RW]

Parent object providing additional commands to the block.

Public Class Methods

add_command_to(destination, options={}) click to toggle source

This is a convenience command that allows this DslBlock to inject itself as a method into another DslBlock or Object. If the parent is also a DslBlock, the new method will automatically be added to the available commands.

Params:

destination

The object to receive the new method

options

A hash of options configuring the command

Options:

:propagate

Allow methods in the destination to be called by the block. (default: false)

:command_name

The name of the method to be created or nil to use the default which is based off of the class name. (default: nil)

# File lib/dsl_block.rb, line 95
def self.add_command_to(destination, options={})
  # Determine the name of the method to create
  command_name = (options[:command_name] || name).to_s.underscore.to_sym
  # Save a reference to our self so we will have something to call in a bit when self will refer to someone else.
  this_class = self
  # Define the command in the destination.
  destination.send(:define_method, command_name) do |&block|
    # Create a new instance of our self with the callers 'self' passed in as an optional parent.
    # Immediately after initialization, yield the block.
    this_class.new(:parent => options[:propagate] ? self : nil, &block).yield
  end
  # Add the new command to the parent if it is a DslBlock.
  destination.commands << command_name if destination.is_a?(Class) && destination < DslBlock
end
commands(*args) click to toggle source

With no arguments, returns an array of command names that this DslBlock makes available to blocks either directly or indirectly. With arguments, adds new names to the array of command names, then returns the new array.

# File lib/dsl_block.rb, line 79
def self.commands(*args)
  @commands ||= []
  @commands = (@commands + args.map(&:to_sym)).uniq
  @commands
end
new(*args, &block) click to toggle source

Create a new DslBlock instance.

block

Required block of code that will be executed when yield is called on the new DslBlock instance.

Options:

:parent

Optional parent DslBlock or Object that is providing additional commands to the block. (default: nil)

:block

Optional method of passing in a block.

# File lib/dsl_block.rb, line 116
def initialize(*args, &block)
  options = args.extract_options!
  @block = block_given? ? block : options[:block]
  raise ArgumentError, 'block must be provided' unless @block
  @parent = options[:parent]
end

Public Instance Methods

_commands() click to toggle source

This is the entire list of commands that this instance makes available to the block of code to be run. It is a combination of three distinct sources.

  1. The class’s declared commands

  2. If there is a parent of this DslBock instance…

    • The parents declared commands if it is a DslBlock

    • The parents public_methods if it is any other type of object

  3. Kernel.methods

This method is prefixed with an underscore in an attempt to avoid collisions with commands in the given block.

# File lib/dsl_block.rb, line 132
def _commands
  cmds = self.class.commands.dup
  if @parent
    if @parent.is_a?(DslBlock)
      cmds += @parent._commands
    else
      cmds +=  @parent.public_methods
    end
  end
  (cmds + Kernel.methods).uniq
end
yield() click to toggle source

Yield the block given.

# File lib/dsl_block.rb, line 145
def yield
  begin
    # Evaluate the block in an executor to provide isolation
    # and prevent accidental interference with ourselves.
    Executor.new(self).instance_eval(&@block)
  rescue Exception => e
    e.set_backtrace(caller.select { |x| !x.include?(__FILE__)})
    raise e
  end
end