# 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