Add documentation of #dsl_eval_immutable

This commit is contained in:
Marc Siegel 2013-07-28 23:57:25 -04:00
parent abd75d6290
commit 62de806d24
3 changed files with 59 additions and 7 deletions

View File

@ -3,7 +3,6 @@ require "docile/fallback_context_proxy"
require "docile/chaining_fallback_context_proxy"
# Docile keeps your Ruby DSLs tame and well-behaved
# @see http://ms-ati.github.com/docile/
module Docile
# Execute a block in the context of an object whose methods represent the
# commands in a DSL.
@ -12,8 +11,9 @@ module Docile
#
# Use this method to execute an *imperative* DSL, which means that:
#
# 1. each command mutates the state of the DSL context object
# 2. the return values of the commands are ignored
# 1. Each command mutates the state of the DSL context object
# 2. The return value of each command is ignored
# 3. The final return value is the original context object
#
# @example Use a String as a DSL
# Docile.dsl_eval("Hello, world!") do
@ -42,6 +42,38 @@ module Docile
end
module_function :dsl_eval
# Execute a block in the context of an immutable object whose methods,
# and the methods of their return values, represent the commands in a DSL.
#
# @note Use with a *functional* DSL (commands return successor
# context objects)
#
# Use this method to execute a *functional* DSL, which means that:
#
# 1. The original DSL context object is never mutated
# 2. Each command returns the next DSL context object
# 3. The final return value is the value returned by the last command
#
# @example Use a String as a DSL
# Docile.dsl_eval_immutable("Hello, world!") do
# reverse
# upcase
# end
# #=> "!DLROW ,OLLEH"
#
# @example Use a Float as a DSL
# Docile.dsl_eval_immutable(84.5) do
# fdiv(2)
# floor
# end
# #=> 42
#
# @param dsl [Object] immutable context object whose methods make up the
# initial DSL
# @param args [Array] arguments to be passed to the block
# @yield the block of DSL commands to be executed against the
# `dsl` context object and successor return values
# @return [Object] the return value of the final command in the block
def dsl_eval_immutable(dsl, *args, &block)
exec_in_proxy_context(dsl, ChainingFallbackContextProxy, *args, &block)
end
@ -49,6 +81,17 @@ module Docile
private
# @api private
#
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, using a specific proxy class.
#
# @param dsl [Object] context object whose methods make up the
# (initial) DSL
# @param proxy_type [Class] class to instantiate as the proxy context
# @param args [Array] arguments to be passed to the block
# @yield the block of DSL commands to be executed
# @return [Object] the return value of the block
def exec_in_proxy_context(dsl, proxy_type, *args, &block)
block_context = eval("self", block.binding)
proxy_context = proxy_type.new(dsl, proxy_type.new(dsl, block_context))

View File

@ -1,13 +1,18 @@
require "docile/fallback_context_proxy"
module Docile
# Operates in the same manner as {FallbackContextProxy}, but replacing
# the primary `receiver` object with the result of each proxied method.
#
# This is useful for implementing DSL evaluation for immutable context
# objects.
#
# @see Docile#dsl_eval_immutable
class ChainingFallbackContextProxy < FallbackContextProxy
# Proxy methods as in {FallbackContextProxy#method_missing}, replacing
# `receiver` with the returned value.
def method_missing(method, *args, &block)
@__receiver__ = super(method, *args, &block)
end
end
end

View File

@ -7,6 +7,10 @@ module Docile
# Will attempt to forward all method calls first to the primary receiver,
# and then to the fallback receiver if the primary does not handle that
# method.
#
# This is useful for implementing DSL evaluation in the context of an object.
#
# @see Docile#dsl_eval
class FallbackContextProxy
# The set of methods which will **not** be proxied, but instead answered
# by this object directly.