lib/dsl_block.rb in dsl_block-1.0.0 vs lib/dsl_block.rb in dsl_block-2.0.0
- old
+ new
@@ -1,159 +1,168 @@
-require 'dsl_block/version'
-require 'dsl_block/executor'
-require 'active_support/core_ext/string/inflections'
-
-# 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, 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
-#
-class DslBlock
- # Parent object providing additional commands to the block.
- attr_accessor :parent
- # Block of code that will be executed upon yield.
- attr_accessor :block
-
- # 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.
- def self.commands(*args)
- @commands ||= []
- @commands = (@commands + args.map(&:to_sym)).uniq
- @commands
- end
-
- # 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
- # +propigate_local_commands+:: 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)
- def self.add_command_to(destination, propigate_local_commands=false, command_name=nil)
- # Determine the name of the method to create
- command_name = (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(propigate_local_commands ? 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
-
- # Create a new DslBlock instance.
- # +parent+:: Optional parent DslBlock or Object that is providing additional commands to the block. (default: nil)
- # +block+:: Required block of code that will be executed when yield is called on the new DslBlock instance.
- def initialize(parent = nil, &block)
- raise ArgumentError, 'block must be provided' unless block_given?
- @block = block
- @parent = parent
- end
-
- # 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.
- 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 the block given.
- 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
-
- # :nodoc:
- def respond_to_missing?(method, include_all)
- @parent && @parent.respond_to?(method, include_all) || super
- end
-
- def method_missing(name, *args, &block)
- if @parent && @parent.respond_to?(name)
- @parent.send(name, *args, &block)
- else
- super
- end
- end
-end
+require 'dsl_block/version'
+require 'dsl_block/executor'
+require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/array/extract_options'
+
+
+# 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
+#
+class DslBlock
+ # Parent object providing additional commands to the block.
+ attr_accessor :parent
+ # Block of code that will be executed upon yield.
+ attr_accessor :block
+
+ # 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.
+ def self.commands(*args)
+ @commands ||= []
+ @commands = (@commands + args.map(&:to_sym)).uniq
+ @commands
+ end
+
+ # 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)
+ 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
+
+ # 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.
+ 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
+
+ # 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.
+ 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 the block given.
+ 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
+
+ # :nodoc:
+ def respond_to_missing?(method, include_all)
+ @parent && @parent.respond_to?(method, include_all) || super
+ end
+
+ def method_missing(name, *args, &block)
+ if @parent && @parent.respond_to?(name)
+ @parent.send(name, *args, &block)
+ else
+ super
+ end
+ end
+end