toys 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/README.md +5 -2
- data/lib/toys.rb +2 -2
- data/lib/toys/builtins/system.rb +4 -0
- data/lib/toys/cli.rb +90 -3
- data/lib/toys/config_dsl.rb +432 -0
- data/lib/toys/context.rb +99 -0
- data/lib/toys/helpers.rb +11 -0
- data/lib/toys/helpers/exec.rb +180 -2
- data/lib/toys/loader.rb +165 -27
- data/lib/toys/middleware.rb +14 -0
- data/lib/toys/middleware/base.rb +7 -1
- data/lib/toys/middleware/set_verbosity.rb +3 -0
- data/lib/toys/middleware/{group_default.rb → show_group_usage.rb} +16 -5
- data/lib/toys/middleware/{show_tool_help.rb → show_tool_usage.rb} +8 -1
- data/lib/toys/middleware/show_usage_errors.rb +6 -0
- data/lib/toys/template.rb +73 -0
- data/lib/toys/templates.rb +14 -0
- data/lib/toys/templates/clean.rb +13 -2
- data/lib/toys/templates/gem_build.rb +19 -1
- data/lib/toys/templates/minitest.rb +17 -2
- data/lib/toys/templates/rubocop.rb +14 -1
- data/lib/toys/templates/yardoc.rb +15 -1
- data/lib/toys/tool.rb +405 -77
- data/lib/toys/utils/usage.rb +55 -11
- data/lib/toys/version.rb +1 -1
- metadata +6 -5
- data/lib/toys/builder.rb +0 -227
data/lib/toys/loader.rb
CHANGED
@@ -29,9 +29,35 @@
|
|
29
29
|
|
30
30
|
module Toys
|
31
31
|
##
|
32
|
-
# The
|
32
|
+
# The Loader service loads tools from configuration files, and finds the
|
33
|
+
# appropriate tool given a set of command line arguments.
|
33
34
|
#
|
34
35
|
class Loader
|
36
|
+
##
|
37
|
+
# Create a Loader
|
38
|
+
#
|
39
|
+
# @param [String,nil] config_dir_name A directory with this name that
|
40
|
+
# appears in the loader path, is treated as a configuration directory
|
41
|
+
# whose contents are loaded into the toys configuration. Optional.
|
42
|
+
# If not provided, toplevel configuration directories are disabled.
|
43
|
+
# @param [String,nil] config_file_name A file with this name that appears
|
44
|
+
# in the loader path, is treated as a toplevel configuration file
|
45
|
+
# whose contents are loaded into the toys configuration. Optional.
|
46
|
+
# If not provided, toplevel configuration files are disabled.
|
47
|
+
# @param [String,nil] index_file_name A file with this name that appears
|
48
|
+
# in any configuration directory (not just a toplevel directory) is
|
49
|
+
# loaded first as a standalone configuration file. If not provided,
|
50
|
+
# standalone configuration files are disabled.
|
51
|
+
# @param [String,nil] preload_file_name A file with this name that appears
|
52
|
+
# in any configuration directory (not just a toplevel directory) is
|
53
|
+
# loaded before any configuration files. It is not treated as a
|
54
|
+
# configuration file in that the configuration DSL is not honored. You
|
55
|
+
# may use such a file to define auxiliary Ruby modules and classes that
|
56
|
+
# used by the tools defined in that directory.
|
57
|
+
# @param [Array] middleware An array of middleware that will be used by
|
58
|
+
# default for all tools loaded by this CLI.
|
59
|
+
# @param [String] root_desc The description of the root tool.
|
60
|
+
#
|
35
61
|
def initialize(config_dir_name: nil, config_file_name: nil,
|
36
62
|
index_file_name: nil, preload_file_name: nil,
|
37
63
|
middleware: [], root_desc: nil)
|
@@ -42,12 +68,22 @@ module Toys
|
|
42
68
|
@middleware = middleware
|
43
69
|
check_init_options
|
44
70
|
@load_worklist = []
|
45
|
-
root_tool = Tool.new([]
|
71
|
+
root_tool = Tool.new([])
|
72
|
+
root_tool.middleware_stack.concat(@middleware)
|
46
73
|
root_tool.long_desc = root_desc if root_desc
|
47
74
|
@tools = {[] => [root_tool, nil]}
|
48
75
|
@max_priority = @min_priority = 0
|
49
76
|
end
|
50
77
|
|
78
|
+
##
|
79
|
+
# Add one or more configuration files/directories to the loader.
|
80
|
+
# This might point to a directory that defines a default set of tools.
|
81
|
+
#
|
82
|
+
# @param [String,Array<String>] paths One or more paths to add.
|
83
|
+
# @param [Boolean] high_priority If true, add these paths at the top of
|
84
|
+
# the priority list. Defaults to false, indicating new paths should
|
85
|
+
# be at the bottom of the priority list.
|
86
|
+
#
|
51
87
|
def add_config_paths(paths, high_priority: false)
|
52
88
|
paths = Array(paths)
|
53
89
|
paths = paths.reverse if high_priority
|
@@ -57,6 +93,15 @@ module Toys
|
|
57
93
|
self
|
58
94
|
end
|
59
95
|
|
96
|
+
##
|
97
|
+
# Add a single configuration file/directory to the loader.
|
98
|
+
# This might point to a directory that defines a default set of tools.
|
99
|
+
#
|
100
|
+
# @param [String] path A path to add.
|
101
|
+
# @param [Boolean] high_priority If true, add this path at the top of the
|
102
|
+
# priority list. Defaults to false, indicating the new path should be
|
103
|
+
# at the bottom of the priority list.
|
104
|
+
#
|
60
105
|
def add_config_path(path, high_priority: false)
|
61
106
|
path = check_path(path)
|
62
107
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
@@ -64,6 +109,15 @@ module Toys
|
|
64
109
|
self
|
65
110
|
end
|
66
111
|
|
112
|
+
##
|
113
|
+
# Add one or more path directories to the loader. These directories are
|
114
|
+
# searched for toplevel config directories and files.
|
115
|
+
#
|
116
|
+
# @param [String,Array<String>] paths One or more paths to add.
|
117
|
+
# @param [Boolean] high_priority If true, add these paths at the top of
|
118
|
+
# the priority list. Defaults to false, indicating new paths should
|
119
|
+
# be at the bottom of the priority list.
|
120
|
+
#
|
67
121
|
def add_paths(paths, high_priority: false)
|
68
122
|
paths = Array(paths)
|
69
123
|
paths = paths.reverse if high_priority
|
@@ -73,6 +127,15 @@ module Toys
|
|
73
127
|
self
|
74
128
|
end
|
75
129
|
|
130
|
+
##
|
131
|
+
# Add a single path directory to the loader. This directory is searched
|
132
|
+
# for toplevel config directories and files.
|
133
|
+
#
|
134
|
+
# @param [String] path A path to add.
|
135
|
+
# @param [Boolean] high_priority If true, add this path at the top of the
|
136
|
+
# priority list. Defaults to false, indicating the new path should be
|
137
|
+
# at the bottom of the priority list.
|
138
|
+
#
|
76
139
|
def add_path(path, high_priority: false)
|
77
140
|
path = check_path(path, type: :dir)
|
78
141
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
@@ -91,6 +154,16 @@ module Toys
|
|
91
154
|
self
|
92
155
|
end
|
93
156
|
|
157
|
+
##
|
158
|
+
# Given a list of command line arguments, find the appropriate tool to
|
159
|
+
# handle the command, loading it from the configuration if necessary.
|
160
|
+
# This always returns a tool. If the specific tool path is not defined and
|
161
|
+
# cannot be found in any configuration, it returns the nearest group that
|
162
|
+
# _would_ contain that tool, up to the root tool.
|
163
|
+
#
|
164
|
+
# @param [String] args Command line arguments
|
165
|
+
# @return [Toys::Tool]
|
166
|
+
#
|
94
167
|
def lookup(args)
|
95
168
|
orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
|
96
169
|
cur_prefix = orig_prefix.dup
|
@@ -105,59 +178,119 @@ module Toys
|
|
105
178
|
end
|
106
179
|
end
|
107
180
|
|
181
|
+
##
|
182
|
+
# Returns a list of subtools for the given path, loading from the
|
183
|
+
# configuration if necessary.
|
184
|
+
#
|
185
|
+
# @param [Array<String>] words The name of the parent tool
|
186
|
+
# @param [Boolean] recursive If true, return all subtools recursively
|
187
|
+
# rather than just the immediate children (the default)
|
188
|
+
# @return [Array<Toys::Tool>]
|
189
|
+
#
|
190
|
+
def list_subtools(words, recursive: false)
|
191
|
+
load_for_prefix(words)
|
192
|
+
found_tools = []
|
193
|
+
len = words.length
|
194
|
+
@tools.each do |n, tp|
|
195
|
+
next if n.empty?
|
196
|
+
if recursive
|
197
|
+
next if n.length <= len || n.slice(0, len) != words
|
198
|
+
else
|
199
|
+
next unless n.slice(0..-2) == words
|
200
|
+
end
|
201
|
+
found_tools << tp.first
|
202
|
+
end
|
203
|
+
sort_tools_by_name(found_tools)
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Execute the tool given by the given arguments, in the given context.
|
208
|
+
#
|
209
|
+
# @param [Toys::Context::Base] context_base The context in which to
|
210
|
+
# execute.
|
211
|
+
# @param [String] args Command line arguments
|
212
|
+
# @param [Integer] verbosity Starting verbosity. Defaults to 0.
|
213
|
+
# @return [Integer] The exit code
|
214
|
+
#
|
215
|
+
# @private
|
216
|
+
#
|
108
217
|
def execute(context_base, args, verbosity: 0)
|
109
218
|
tool = lookup(args)
|
110
219
|
tool.execute(context_base, args.slice(tool.full_name.length..-1), verbosity: verbosity)
|
111
220
|
end
|
112
221
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
222
|
+
##
|
223
|
+
# Returns a tool specified by the given words, with the given priority.
|
224
|
+
# Does not do any loading. If the tool is not present, creates it.
|
225
|
+
#
|
226
|
+
# @param [Array<String>] words The name of the tool.
|
227
|
+
# @param [Integer] priority The priority of the request.
|
228
|
+
# @param [Boolean] assume_parent If true, does not check the parent tool's
|
229
|
+
# priority.
|
230
|
+
# @return [Toys::Tool,nil] The tool, or `nil` if the given priority is
|
231
|
+
# insufficient.
|
232
|
+
#
|
233
|
+
# @private
|
234
|
+
#
|
235
|
+
def get_or_create_tool(words, priority, assume_parent: false)
|
119
236
|
if tool_defined?(words)
|
120
237
|
tool, tool_priority = @tools[words]
|
121
238
|
return tool if priority.nil? || tool_priority.nil? || tool_priority == priority
|
122
239
|
return nil if tool_priority > priority
|
123
240
|
end
|
124
241
|
unless assume_parent
|
125
|
-
parent =
|
242
|
+
parent = get_or_create_tool(words[0..-2], priority)
|
126
243
|
return nil if parent.nil?
|
127
244
|
end
|
128
|
-
|
245
|
+
prune_from(words)
|
246
|
+
tool = Tool.new(words)
|
247
|
+
tool.middleware_stack.concat(@middleware)
|
129
248
|
@tools[words] = [tool, priority]
|
130
249
|
tool
|
131
250
|
end
|
132
251
|
|
252
|
+
##
|
253
|
+
# Adds a tool directly to the loader.
|
254
|
+
# This should be used only for testing, as it overrides normal priority
|
255
|
+
# checking.
|
256
|
+
#
|
257
|
+
# @param [Toys::Tool] tool Tool to add.
|
258
|
+
# @param [Integer,nil] priority Priority for the tool.
|
259
|
+
#
|
260
|
+
# @private
|
261
|
+
#
|
133
262
|
def put_tool!(tool, priority = nil)
|
134
263
|
@tools[tool.full_name] = [tool, priority]
|
135
264
|
self
|
136
265
|
end
|
137
266
|
|
267
|
+
##
|
268
|
+
# Returns true if the given tool name currently exists in the loader.
|
269
|
+
# Does not load the tool if not found.
|
270
|
+
#
|
271
|
+
# @param [Array<String>] words The name of the tool.
|
272
|
+
# @return [Boolean]
|
273
|
+
#
|
274
|
+
# @private
|
275
|
+
#
|
138
276
|
def tool_defined?(words)
|
139
277
|
@tools.key?(words)
|
140
278
|
end
|
141
279
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if recursive
|
148
|
-
next if n.length <= len || n.slice(0, len) != words
|
149
|
-
else
|
150
|
-
next unless n.slice(0..-2) == words
|
151
|
-
end
|
152
|
-
found_tools << tp.first
|
153
|
-
end
|
154
|
-
sort_tools_by_name(found_tools)
|
155
|
-
end
|
156
|
-
|
280
|
+
##
|
281
|
+
# Load configuration from the given path.
|
282
|
+
#
|
283
|
+
# @private
|
284
|
+
#
|
157
285
|
def include_path(path, words, remaining_words, priority)
|
158
286
|
handle_path(check_path(path), words, remaining_words, priority)
|
159
287
|
end
|
160
288
|
|
289
|
+
##
|
290
|
+
# Determine the next setting for remaining_words, given a word.
|
291
|
+
#
|
292
|
+
# @private
|
293
|
+
#
|
161
294
|
def self.next_remaining_words(remaining_words, word)
|
162
295
|
if remaining_words.nil?
|
163
296
|
nil
|
@@ -185,6 +318,11 @@ module Toys
|
|
185
318
|
end
|
186
319
|
end
|
187
320
|
|
321
|
+
def prune_from(words)
|
322
|
+
return unless @tools.key?(words)
|
323
|
+
@tools.delete_if { |k, _v| k[0, words.size] == words }
|
324
|
+
end
|
325
|
+
|
188
326
|
def load_for_prefix(prefix)
|
189
327
|
cur_worklist = @load_worklist
|
190
328
|
@load_worklist = []
|
@@ -203,9 +341,9 @@ module Toys
|
|
203
341
|
|
204
342
|
def load_path(path, words, remaining_words, priority)
|
205
343
|
if ::File.extname(path) == ".rb"
|
206
|
-
tool =
|
344
|
+
tool = get_or_create_tool(words, priority)
|
207
345
|
if tool
|
208
|
-
|
346
|
+
ConfigDSL.evaluate(path, tool, remaining_words, priority, self, :tool, ::IO.read(path))
|
209
347
|
end
|
210
348
|
else
|
211
349
|
require_preload_in(path)
|
data/lib/toys/middleware.rb
CHANGED
@@ -34,6 +34,20 @@ module Toys
|
|
34
34
|
# Namespace for common middleware
|
35
35
|
#
|
36
36
|
module Middleware
|
37
|
+
##
|
38
|
+
# Return a middleware class by name.
|
39
|
+
#
|
40
|
+
# Currently recognized middleware names are:
|
41
|
+
#
|
42
|
+
# * `:group_default` : Provides a default implementation for a group.
|
43
|
+
# * `:set_verbosity` : Switches for affecting log verbosity.
|
44
|
+
# * `:show_tool_help` : A switch that causes a tool to print its usage
|
45
|
+
# documentation.
|
46
|
+
# * `:show_usage_errors` : Displays the usage error if one occurs.
|
47
|
+
#
|
48
|
+
# @param [String,Symbol] name Name of the middleware class to return
|
49
|
+
# @return [Class,nil] The class, or `nil` if not found
|
50
|
+
#
|
37
51
|
def self.lookup(name)
|
38
52
|
Utils::ModuleLookup.lookup(:middleware, name)
|
39
53
|
end
|
data/lib/toys/middleware/base.rb
CHANGED
@@ -30,13 +30,19 @@
|
|
30
30
|
module Toys
|
31
31
|
module Middleware
|
32
32
|
##
|
33
|
-
# A base middleware with a no-op implementation
|
33
|
+
# A base middleware with a no-op implementation.
|
34
34
|
#
|
35
35
|
class Base
|
36
|
+
##
|
37
|
+
# The base middleware does not affect tool configuration.
|
38
|
+
#
|
36
39
|
def config(_tool)
|
37
40
|
yield
|
38
41
|
end
|
39
42
|
|
43
|
+
##
|
44
|
+
# The base middleware does not affect tool execution.
|
45
|
+
#
|
40
46
|
def execute(_context)
|
41
47
|
yield
|
42
48
|
end
|
@@ -35,6 +35,9 @@ module Toys
|
|
35
35
|
# A middleware that provides switches for editing the verbosity
|
36
36
|
#
|
37
37
|
class SetVerbosity < Base
|
38
|
+
##
|
39
|
+
# This middleware adds `--verbose` and `--quiet` flags.
|
40
|
+
#
|
38
41
|
def config(tool)
|
39
42
|
tool.add_switch(Context::VERBOSITY, "-v", "--verbose",
|
40
43
|
doc: "Increase verbosity",
|
@@ -33,23 +33,34 @@ require "toys/utils/usage"
|
|
33
33
|
module Toys
|
34
34
|
module Middleware
|
35
35
|
##
|
36
|
-
# A middleware that provides a default implementation for groups
|
36
|
+
# A middleware that provides a default implementation for groups. If a
|
37
|
+
# tool has no executor, this middleware assumes it to be a group, and it
|
38
|
+
# provides a default executor that displays group usage documentation.
|
37
39
|
#
|
38
|
-
class
|
40
|
+
class ShowGroupUsage < Base
|
41
|
+
##
|
42
|
+
# This middleware adds a "--no-recursive" flag to groups. This flag, when
|
43
|
+
# set, shows only immediate subcommands rather than all recursively.
|
44
|
+
#
|
39
45
|
def config(tool)
|
40
46
|
if tool.includes_executor?
|
41
47
|
yield
|
42
48
|
else
|
43
|
-
tool.add_switch(:
|
44
|
-
doc: "Show all subcommands
|
49
|
+
tool.add_switch(:_no_recursive, "--no-recursive",
|
50
|
+
doc: "Show immediate rather than all subcommands",
|
51
|
+
only_unique: true)
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
55
|
+
##
|
56
|
+
# This middleware displays the usage documentation for groups. It has
|
57
|
+
# no effect on tools that have their own executor.
|
58
|
+
#
|
48
59
|
def execute(context)
|
49
60
|
if context[Context::TOOL].includes_executor?
|
50
61
|
yield
|
51
62
|
else
|
52
|
-
puts(Utils::Usage.from_context(context).string(recursive: context[:
|
63
|
+
puts(Utils::Usage.from_context(context).string(recursive: !context[:_no_recursive]))
|
53
64
|
end
|
54
65
|
end
|
55
66
|
end
|
@@ -35,7 +35,10 @@ module Toys
|
|
35
35
|
##
|
36
36
|
# A middleware that shows usage documentation
|
37
37
|
#
|
38
|
-
class
|
38
|
+
class ShowToolUsage < Base
|
39
|
+
##
|
40
|
+
# This middleware adds a `--help` flag that triggers display of help.
|
41
|
+
#
|
39
42
|
def config(tool)
|
40
43
|
if tool.includes_executor?
|
41
44
|
tool.add_switch(:_help, "-?", "--help",
|
@@ -45,6 +48,10 @@ module Toys
|
|
45
48
|
yield
|
46
49
|
end
|
47
50
|
|
51
|
+
##
|
52
|
+
# If the `--help` flag is present, this middleware causes the tool to
|
53
|
+
# display its usage documentation and exit, rather than executing.
|
54
|
+
#
|
48
55
|
def execute(context)
|
49
56
|
if context[:_help]
|
50
57
|
puts(Utils::Usage.from_context(context).string(recursive: context[:_recursive]))
|
@@ -36,6 +36,12 @@ module Toys
|
|
36
36
|
# A middleware that shows usage errors
|
37
37
|
#
|
38
38
|
class ShowUsageErrors < Base
|
39
|
+
##
|
40
|
+
# If a usage error happens, e.g. an unrecognized switch or an unfulfilled
|
41
|
+
# required argument, this middleware causes the tool to display the error
|
42
|
+
# and usage documentation and exit with a nonzero result. Otherwise, it
|
43
|
+
# does nothing.
|
44
|
+
#
|
39
45
|
def execute(context)
|
40
46
|
if context[Context::USAGE_ERROR]
|
41
47
|
puts(context[Context::USAGE_ERROR])
|
data/lib/toys/template.rb
CHANGED
@@ -31,7 +31,73 @@ module Toys
|
|
31
31
|
##
|
32
32
|
# A template definition. Template classes should include this module.
|
33
33
|
#
|
34
|
+
# A template is a configurable set of DSL code that can be run in a toys
|
35
|
+
# configuration to automate tool defintion. For example, toys provides a
|
36
|
+
# "minitest" template that generates a "test" tool that invokes minitest.
|
37
|
+
# Templates will often support configuration; for example the minitest
|
38
|
+
# template lets you configure the paths to the test files.
|
39
|
+
#
|
40
|
+
# ## Usage
|
41
|
+
#
|
42
|
+
# To create a template, define a class and include this module.
|
43
|
+
# The class defines the "configuration" of the template. If your template
|
44
|
+
# has options/parameters, you should provide a constructor, and methods
|
45
|
+
# appropriate to edit those options. The arguments given to the
|
46
|
+
# {Toys::ConfigDSL#expand} method are passed to your constructor, and your
|
47
|
+
# template object is passed to any block given to {Toys::ConfigDSL#expand}.
|
48
|
+
#
|
49
|
+
# Next, in your template class, call the `to_expand` method, which is defined
|
50
|
+
# in {Toys::Template::ClassMethods#to_expand}. Pass this a block which
|
51
|
+
# defines the implementation of the template. Effectively, the contents of
|
52
|
+
# this block are "inserted" into the user's configuration. The template
|
53
|
+
# object is passed to the block so you have access to the template options.
|
54
|
+
#
|
55
|
+
# ## Example
|
56
|
+
#
|
57
|
+
# This is a simple template that generates a "hello" tool. The tool simply
|
58
|
+
# prints a `"Hello, #{name}!"` greeting. The name is set as a template
|
59
|
+
# option; it is defined when the template is expanded in a toys
|
60
|
+
# configuration.
|
61
|
+
#
|
62
|
+
# # Define a template by creating a class that includes Toys::Template.
|
63
|
+
# class MyHelloTemplate
|
64
|
+
# include Toys::Template
|
65
|
+
#
|
66
|
+
# # A user of the template may pass an optional name as a parameter to
|
67
|
+
# # `expand`, or leave it as the default of "world".
|
68
|
+
# def initialize(name: "world")
|
69
|
+
# @name = name
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# # The template is passed to the expand block, so a user of the
|
73
|
+
# # template may also call this method to set the name.
|
74
|
+
# attr_accessor :name
|
75
|
+
#
|
76
|
+
# # The following block is inserted when the template is expanded.
|
77
|
+
# to_expand do |template|
|
78
|
+
# desc "Prints a greeting to #{template.name}"
|
79
|
+
# tool "templated-greeting" do
|
80
|
+
# execute do
|
81
|
+
# puts "Hello, #{template.name}!"
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Now you can use the template in your `.toys.rb` file like this:
|
88
|
+
#
|
89
|
+
# expand(MyHelloTemplate, name: "rubyists")
|
90
|
+
#
|
91
|
+
# or alternately:
|
92
|
+
#
|
93
|
+
# expand(MyHelloTemplate) do |template|
|
94
|
+
# template.name = "rubyists"
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# And it will create a tool called "templated-greeting".
|
98
|
+
#
|
34
99
|
module Template
|
100
|
+
## @private
|
35
101
|
def self.included(mod)
|
36
102
|
mod.extend(ClassMethods)
|
37
103
|
end
|
@@ -40,10 +106,17 @@ module Toys
|
|
40
106
|
# Class methods that will be added to a template class.
|
41
107
|
#
|
42
108
|
module ClassMethods
|
109
|
+
##
|
110
|
+
# Provide the block that implements the template.
|
111
|
+
#
|
43
112
|
def to_expand(&block)
|
44
113
|
@expander = block
|
45
114
|
end
|
46
115
|
|
116
|
+
##
|
117
|
+
# You may alternately set the expander block using this accessor.
|
118
|
+
# @return [Proc]
|
119
|
+
#
|
47
120
|
attr_accessor :expander
|
48
121
|
end
|
49
122
|
end
|