lib/docile.rb
# frozen_string_literal: true
require "docile/version"
require "docile/execution"
require "docile/fallback_context_proxy"
require "docile/chaining_fallback_context_proxy"
require "docile/backtrace_filter"
# Docile keeps your Ruby DSLs tame and well-behaved.
module Docile
extend Execution
# Execute a block in the context of an object whose methods represent the
# commands in a DSL.
#
# @note Use with an *imperative* DSL (commands modify the context object)
#
# 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 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
# reverse!
# upcase!
# end
# #=> "!DLROW ,OLLEH"
#
# @example Use an Array as a DSL
# Docile.dsl_eval([]) do
# push 1
# push 2
# pop
# push 3
# end
# #=> [1, 3]
#
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the `dsl` context object after executing the block
def dsl_eval(dsl, *args, &block)
exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
dsl
end
ruby2_keywords :dsl_eval if respond_to?(:ruby2_keywords, true)
module_function :dsl_eval
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, and return *the block's return value*.
#
# @note Use with an *imperative* DSL (commands modify the context object)
#
# 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 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_with_block_return("Hello, world!") do
# reverse!
# upcase!
# first
# end
# #=> "!"
#
# @example Use an Array as a DSL
# Docile.dsl_eval_with_block_return([]) do
# push "a"
# push "b"
# pop
# push "c"
# length
# end
# #=> 2
#
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the return value from executing the block
def dsl_eval_with_block_return(dsl, *args, &block)
exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
end
if respond_to?(:ruby2_keywords, true)
ruby2_keywords :dsl_eval_with_block_return
end
module_function :dsl_eval_with_block_return
# 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 frozen String as a DSL
# Docile.dsl_eval_immutable("I'm immutable!".freeze) do
# reverse
# upcase
# end
# #=> "!ELBATUMMI M'I"
#
# @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
# @param block [Proc] 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
ruby2_keywords :dsl_eval_immutable if respond_to?(:ruby2_keywords, true)
module_function :dsl_eval_immutable
end