From 5d59cf33cf926091510270fafb5eca79a6df3907 Mon Sep 17 00:00:00 2001 From: Christina Koller Date: Thu, 11 Jan 2018 14:27:50 -0500 Subject: [PATCH] Add entry point that returns block output --- README.md | 27 +++++++++++++++++++++++++++ lib/docile.rb | 39 +++++++++++++++++++++++++++++++++++++++ spec/docile_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/README.md b/README.md index 3518b38..4397864 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,33 @@ end All set! +### Accessing the block's return value + +Sometimes you might want to access the return value of your provided block, +as opposed to the DSL object itself. In these cases, use +`dsl_eval_with_block_return`. It behaves exactly like `dsl_eval`, but returns +the output from executing the block, rather than the DSL object. + +```ruby +arr = [] +with_array(arr) do + push "a" + push "b" + push "c" + length +end +#=> 3 + +arr +#=> ["a", "b", "c"] +``` + +```ruby +def with_array(arr=[], &block) + Docile.dsl_eval_with_block_return(arr, &block) +end +``` + ## Features 1. Method lookup falls back from the DSL object to the block's context diff --git a/lib/docile.rb b/lib/docile.rb index dc51096..71f0161 100644 --- a/lib/docile.rb +++ b/lib/docile.rb @@ -45,6 +45,45 @@ module Docile end 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("Hello, world!") do + # reverse! + # upcase! + # first + # end + # #=> "!" + # + # @example Use an Array as a DSL + # Docile.dsl_eval([]) 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 + 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. # diff --git a/spec/docile_spec.rb b/spec/docile_spec.rb index 33bc896..f7e93f1 100644 --- a/spec/docile_spec.rb +++ b/spec/docile_spec.rb @@ -263,6 +263,29 @@ describe Docile do end + describe '.dsl_eval_with_block_return' do + let(:array) { [] } + let!(:result) { execute_dsl_against_array } + + def execute_dsl_against_array + Docile.dsl_eval_with_block_return(array) do + push 1 + push 2 + pop + push 3 + 'Return me!' + end + end + + it 'executes the block against the DSL context object' do + expect(array).to eq([1, 3]) + end + + it "returns the block's return value" do + expect(result).to eq('Return me!') + end + end + describe '.dsl_eval_immutable' do context 'when DSL context object is a frozen String' do