/
docile.rb
130 lines (123 loc) · 4.36 KB
/
docile.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
ruby2_keywords :dsl_eval_with_block_return if respond_to?(:ruby2_keywords, true)
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