toys-core 0.3.7.1 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +1 -2
- data/lib/toys-core.rb +23 -8
- data/lib/toys/cli.rb +62 -23
- data/lib/toys/core_version.rb +1 -1
- data/lib/toys/definition/arg.rb +0 -2
- data/lib/toys/definition/flag.rb +2 -4
- data/lib/toys/definition/tool.rb +38 -36
- data/lib/toys/dsl/arg.rb +4 -0
- data/lib/toys/dsl/flag.rb +9 -5
- data/lib/toys/dsl/tool.rb +35 -28
- data/lib/toys/input_file.rb +2 -1
- data/lib/toys/loader.rb +97 -51
- data/lib/toys/middleware.rb +61 -87
- data/lib/toys/runner.rb +19 -2
- data/lib/toys/{middleware → standard_middleware}/add_verbosity_flags.rb +24 -8
- data/lib/toys/{middleware → standard_middleware}/handle_usage_errors.rb +4 -6
- data/lib/toys/{middleware → standard_middleware}/set_default_descriptions.rb +4 -4
- data/lib/toys/{middleware → standard_middleware}/show_help.rb +32 -16
- data/lib/toys/{middleware → standard_middleware}/show_root_version.rb +4 -5
- data/lib/toys/{helpers → standard_mixins}/exec.rb +8 -8
- data/lib/toys/{helpers → standard_mixins}/fileutils.rb +1 -1
- data/lib/toys/{helpers → standard_mixins}/highline.rb +2 -3
- data/lib/toys/{helpers → standard_mixins}/terminal.rb +2 -4
- data/lib/toys/tool.rb +3 -2
- data/lib/toys/utils/exec.rb +255 -82
- data/lib/toys/utils/gems.rb +3 -3
- data/lib/toys/utils/help_text.rb +0 -2
- data/lib/toys/utils/module_lookup.rb +60 -40
- data/lib/toys/utils/wrappable_string.rb +0 -2
- metadata +11 -19
- data/lib/toys/helpers.rb +0 -54
- data/lib/toys/middleware/base.rb +0 -99
- data/lib/toys/templates.rb +0 -55
- data/lib/toys/templates/clean.rb +0 -85
- data/lib/toys/templates/gem_build.rb +0 -125
- data/lib/toys/templates/minitest.rb +0 -125
- data/lib/toys/templates/rubocop.rb +0 -86
- data/lib/toys/templates/yardoc.rb +0 -101
data/lib/toys/runner.rb
CHANGED
@@ -31,15 +31,32 @@ require "optparse"
|
|
31
31
|
|
32
32
|
module Toys
|
33
33
|
##
|
34
|
-
# An internal class that
|
35
|
-
#
|
34
|
+
# An internal class that orchestrates execution of a tool.
|
35
|
+
#
|
36
|
+
# Generaly, you should not need to use this class directly. Instead, run a
|
37
|
+
# tool using {Toys::CLI#run}.
|
36
38
|
#
|
37
39
|
class Runner
|
40
|
+
##
|
41
|
+
# Create a runner for a particular tool in a particular CLI.
|
42
|
+
#
|
43
|
+
# @param [Toys::CLI] cli The CLI that is running the tool. This will
|
44
|
+
# provide needed context information.
|
45
|
+
# @param [Toys::Definition::Tool] tool_definition The tool to run.
|
46
|
+
#
|
38
47
|
def initialize(cli, tool_definition)
|
39
48
|
@cli = cli
|
40
49
|
@tool_definition = tool_definition
|
41
50
|
end
|
42
51
|
|
52
|
+
##
|
53
|
+
# Run the tool, provided given arguments.
|
54
|
+
#
|
55
|
+
# @param [Array<String>] args Command line arguments passed to the tool.
|
56
|
+
# @param [Integer] verbosity Initial verbosity. Default is 0.
|
57
|
+
#
|
58
|
+
# @return [Integer] The resulting status code
|
59
|
+
#
|
43
60
|
def run(args, verbosity: 0)
|
44
61
|
data = create_data(args, verbosity)
|
45
62
|
parse_args(args, data)
|
@@ -27,10 +27,8 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/middleware/base"
|
31
|
-
|
32
30
|
module Toys
|
33
|
-
module
|
31
|
+
module StandardMiddleware
|
34
32
|
##
|
35
33
|
# A middleware that provides flags for editing the verbosity.
|
36
34
|
#
|
@@ -38,7 +36,9 @@ module Toys
|
|
38
36
|
# not already defined by the tool. These flags affect the setting of
|
39
37
|
# {Toys::Tool::Keys::VERBOSITY}, and, thus, the logger level.
|
40
38
|
#
|
41
|
-
class AddVerbosityFlags
|
39
|
+
class AddVerbosityFlags
|
40
|
+
include Middleware
|
41
|
+
|
42
42
|
##
|
43
43
|
# Default verbose flags
|
44
44
|
# @return [Array<String>]
|
@@ -56,12 +56,15 @@ module Toys
|
|
56
56
|
#
|
57
57
|
# @param [Boolean,Array<String>,Proc] verbose_flags Specify flags
|
58
58
|
# to increase verbosity. The value may be any of the following:
|
59
|
+
#
|
59
60
|
# * An array of flags that increase verbosity.
|
60
61
|
# * The `true` value to use {DEFAULT_VERBOSE_FLAGS}. (Default)
|
61
62
|
# * The `false` value to disable verbose flags.
|
62
63
|
# * A proc that takes a tool and returns any of the above.
|
64
|
+
#
|
63
65
|
# @param [Boolean,Array<String>,Proc] quiet_flags Specify flags
|
64
66
|
# to decrease verbosity. The value may be any of the following:
|
67
|
+
#
|
65
68
|
# * An array of flags that decrease verbosity.
|
66
69
|
# * The `true` value to use {DEFAULT_QUIET_FLAGS}. (Default)
|
67
70
|
# * The `false` value to disable quiet flags.
|
@@ -84,8 +87,8 @@ module Toys
|
|
84
87
|
private
|
85
88
|
|
86
89
|
def add_verbose_flags(tool_definition)
|
87
|
-
verbose_flags =
|
88
|
-
|
90
|
+
verbose_flags = resolve_flags_spec(@verbose_flags, tool_definition,
|
91
|
+
DEFAULT_VERBOSE_FLAGS)
|
89
92
|
unless verbose_flags.empty?
|
90
93
|
tool_definition.add_flag(
|
91
94
|
Tool::Keys::VERBOSITY, verbose_flags,
|
@@ -98,8 +101,7 @@ module Toys
|
|
98
101
|
end
|
99
102
|
|
100
103
|
def add_quiet_flags(tool_definition)
|
101
|
-
quiet_flags =
|
102
|
-
DEFAULT_QUIET_FLAGS)
|
104
|
+
quiet_flags = resolve_flags_spec(@quiet_flags, tool_definition, DEFAULT_QUIET_FLAGS)
|
103
105
|
unless quiet_flags.empty?
|
104
106
|
tool_definition.add_flag(
|
105
107
|
Tool::Keys::VERBOSITY, quiet_flags,
|
@@ -110,6 +112,20 @@ module Toys
|
|
110
112
|
)
|
111
113
|
end
|
112
114
|
end
|
115
|
+
|
116
|
+
def resolve_flags_spec(flags, tool, defaults)
|
117
|
+
flags = flags.call(tool) if flags.respond_to?(:call)
|
118
|
+
case flags
|
119
|
+
when true, :default
|
120
|
+
Array(defaults)
|
121
|
+
when ::String
|
122
|
+
[flags]
|
123
|
+
when ::Array
|
124
|
+
flags
|
125
|
+
else
|
126
|
+
[]
|
127
|
+
end
|
128
|
+
end
|
113
129
|
end
|
114
130
|
end
|
115
131
|
end
|
@@ -27,19 +27,17 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/middleware/base"
|
31
|
-
require "toys/utils/help_text"
|
32
|
-
require "toys/utils/terminal"
|
33
|
-
|
34
30
|
module Toys
|
35
|
-
module
|
31
|
+
module StandardMiddleware
|
36
32
|
##
|
37
33
|
# This middleware handles the case of a usage error. If a usage error, such
|
38
34
|
# as an unrecognized flag or an unfulfilled required argument, is detected,
|
39
35
|
# this middleware intercepts execution and displays the error along with
|
40
36
|
# the short help string, and terminates execution with an error code.
|
41
37
|
#
|
42
|
-
class HandleUsageErrors
|
38
|
+
class HandleUsageErrors
|
39
|
+
include Middleware
|
40
|
+
|
43
41
|
##
|
44
42
|
# Create a HandleUsageErrors middleware.
|
45
43
|
#
|
@@ -27,10 +27,8 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/middleware/base"
|
31
|
-
|
32
30
|
module Toys
|
33
|
-
module
|
31
|
+
module StandardMiddleware
|
34
32
|
##
|
35
33
|
# This middleware sets default description fields for tools and command
|
36
34
|
# line arguments and flags that do not have them set otherwise.
|
@@ -39,7 +37,9 @@ module Toys
|
|
39
37
|
# root tool by passing parameters to this middleware. For finer control,
|
40
38
|
# you can override methods to modify the description generation logic.
|
41
39
|
#
|
42
|
-
class SetDefaultDescriptions
|
40
|
+
class SetDefaultDescriptions
|
41
|
+
include Middleware
|
42
|
+
|
43
43
|
##
|
44
44
|
# The default description for tools.
|
45
45
|
# @return [String]
|
@@ -27,13 +27,8 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/middleware/base"
|
31
|
-
require "toys/utils/exec"
|
32
|
-
require "toys/utils/help_text"
|
33
|
-
require "toys/utils/terminal"
|
34
|
-
|
35
30
|
module Toys
|
36
|
-
module
|
31
|
+
module StandardMiddleware
|
37
32
|
##
|
38
33
|
# A middleware that shows help text for the tool when a flag (typically
|
39
34
|
# `--help`) is provided. It can also be configured to show help by
|
@@ -44,7 +39,9 @@ module Toys
|
|
44
39
|
# all subtools recursively rather than only immediate subtools. This
|
45
40
|
# middleware can also search for keywords in its subtools.
|
46
41
|
#
|
47
|
-
class ShowHelp
|
42
|
+
class ShowHelp
|
43
|
+
include Middleware
|
44
|
+
|
48
45
|
##
|
49
46
|
# Default help flags
|
50
47
|
# @return [Array<String>]
|
@@ -74,30 +71,38 @@ module Toys
|
|
74
71
|
#
|
75
72
|
# @param [Boolean,Array<String>,Proc] help_flags Specify flags to
|
76
73
|
# display help. The value may be any of the following:
|
74
|
+
#
|
77
75
|
# * An array of flags.
|
78
76
|
# * The `true` value to use {DEFAULT_HELP_FLAGS}.
|
79
77
|
# * The `false` value for no flags. (Default)
|
80
78
|
# * A proc that takes a tool and returns any of the above.
|
79
|
+
#
|
81
80
|
# @param [Boolean,Array<String>,Proc] usage_flags Specify flags to
|
82
81
|
# display usage. The value may be any of the following:
|
82
|
+
#
|
83
83
|
# * An array of flags.
|
84
84
|
# * The `true` value to use {DEFAULT_USAGE_FLAGS}.
|
85
85
|
# * The `false` value for no flags. (Default)
|
86
86
|
# * A proc that takes a tool and returns any of the above.
|
87
|
+
#
|
87
88
|
# @param [Boolean,Array<String>,Proc] recursive_flags Specify flags
|
88
89
|
# to control recursive subtool search. The value may be any of the
|
89
90
|
# following:
|
91
|
+
#
|
90
92
|
# * An array of flags.
|
91
93
|
# * The `true` value to use {DEFAULT_RECURSIVE_FLAGS}.
|
92
94
|
# * The `false` value for no flags. (Default)
|
93
95
|
# * A proc that takes a tool and returns any of the above.
|
96
|
+
#
|
94
97
|
# @param [Boolean,Array<String>,Proc] search_flags Specify flags
|
95
98
|
# to search subtools for a search term. The value may be any of
|
96
99
|
# the following:
|
100
|
+
#
|
97
101
|
# * An array of flags.
|
98
102
|
# * The `true` value to use {DEFAULT_SEARCH_FLAGS}.
|
99
103
|
# * The `false` value for no flags. (Default)
|
100
104
|
# * A proc that takes a tool and returns any of the above.
|
105
|
+
#
|
101
106
|
# @param [Boolean] default_recursive Whether to search recursively for
|
102
107
|
# subtools by default. Default is `false`.
|
103
108
|
# @param [Boolean] fallback_execution Cause the tool to display its own
|
@@ -191,7 +196,7 @@ module Toys
|
|
191
196
|
|
192
197
|
def output_help(str)
|
193
198
|
if less_path
|
194
|
-
Utils::Exec.new.exec([less_path, "-R"],
|
199
|
+
Utils::Exec.new.exec([less_path, "-R"], in: [:string, str])
|
195
200
|
else
|
196
201
|
terminal.puts(str)
|
197
202
|
end
|
@@ -226,8 +231,7 @@ module Toys
|
|
226
231
|
end
|
227
232
|
|
228
233
|
def add_help_flags(tool_definition)
|
229
|
-
help_flags =
|
230
|
-
DEFAULT_HELP_FLAGS)
|
234
|
+
help_flags = resolve_flags_spec(@help_flags, tool_definition, DEFAULT_HELP_FLAGS)
|
231
235
|
unless help_flags.empty?
|
232
236
|
tool_definition.add_flag(
|
233
237
|
:_show_help, help_flags,
|
@@ -239,8 +243,7 @@ module Toys
|
|
239
243
|
end
|
240
244
|
|
241
245
|
def add_usage_flags(tool_definition)
|
242
|
-
usage_flags =
|
243
|
-
DEFAULT_USAGE_FLAGS)
|
246
|
+
usage_flags = resolve_flags_spec(@usage_flags, tool_definition, DEFAULT_USAGE_FLAGS)
|
244
247
|
unless usage_flags.empty?
|
245
248
|
tool_definition.add_flag(
|
246
249
|
:_show_usage, usage_flags,
|
@@ -252,8 +255,8 @@ module Toys
|
|
252
255
|
end
|
253
256
|
|
254
257
|
def add_recursive_flags(tool_definition)
|
255
|
-
recursive_flags =
|
256
|
-
|
258
|
+
recursive_flags = resolve_flags_spec(@recursive_flags, tool_definition,
|
259
|
+
DEFAULT_RECURSIVE_FLAGS)
|
257
260
|
unless recursive_flags.empty?
|
258
261
|
tool_definition.add_flag(
|
259
262
|
:_recursive_subtools, recursive_flags,
|
@@ -264,8 +267,7 @@ module Toys
|
|
264
267
|
end
|
265
268
|
|
266
269
|
def add_search_flags(tool_definition)
|
267
|
-
search_flags =
|
268
|
-
DEFAULT_SEARCH_FLAGS)
|
270
|
+
search_flags = resolve_flags_spec(@search_flags, tool_definition, DEFAULT_SEARCH_FLAGS)
|
269
271
|
unless search_flags.empty?
|
270
272
|
tool_definition.add_flag(
|
271
273
|
:_search_subtools, search_flags,
|
@@ -274,6 +276,20 @@ module Toys
|
|
274
276
|
)
|
275
277
|
end
|
276
278
|
end
|
279
|
+
|
280
|
+
def resolve_flags_spec(flags, tool, defaults)
|
281
|
+
flags = flags.call(tool) if flags.respond_to?(:call)
|
282
|
+
case flags
|
283
|
+
when true, :default
|
284
|
+
Array(defaults)
|
285
|
+
when ::String
|
286
|
+
[flags]
|
287
|
+
when ::Array
|
288
|
+
flags
|
289
|
+
else
|
290
|
+
[]
|
291
|
+
end
|
292
|
+
end
|
277
293
|
end
|
278
294
|
end
|
279
295
|
end
|
@@ -27,16 +27,15 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/middleware/base"
|
31
|
-
require "toys/utils/terminal"
|
32
|
-
|
33
30
|
module Toys
|
34
|
-
module
|
31
|
+
module StandardMiddleware
|
35
32
|
##
|
36
33
|
# A middleware that displays a version string for the root tool if the
|
37
34
|
# `--version` flag is given.
|
38
35
|
#
|
39
|
-
class ShowRootVersion
|
36
|
+
class ShowRootVersion
|
37
|
+
include Middleware
|
38
|
+
|
40
39
|
##
|
41
40
|
# Default version flags
|
42
41
|
# @return [Array<String>]
|
@@ -27,10 +27,8 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/utils/exec"
|
31
|
-
|
32
30
|
module Toys
|
33
|
-
module
|
31
|
+
module StandardMixins
|
34
32
|
##
|
35
33
|
# A set of helper methods for invoking subcommands. Provides shortcuts for
|
36
34
|
# common cases such as invoking Ruby in a subprocess or capturing output
|
@@ -69,8 +67,8 @@ module Toys
|
|
69
67
|
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
|
70
68
|
# the subprocess streams.
|
71
69
|
#
|
72
|
-
# @return [Toys::Utils::Result] The subprocess result, including
|
73
|
-
# code and any captured output.
|
70
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
71
|
+
# the exit code and any captured output.
|
74
72
|
#
|
75
73
|
def exec(cmd, opts = {}, &block)
|
76
74
|
Exec._exec(self).exec(cmd, Exec._setup_exec_opts(opts, self), &block)
|
@@ -88,8 +86,8 @@ module Toys
|
|
88
86
|
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
|
89
87
|
# for the subprocess streams.
|
90
88
|
#
|
91
|
-
# @return [Toys::Utils::Result] The subprocess result, including
|
92
|
-
# code and any captured output.
|
89
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
90
|
+
# the exit code and any captured output.
|
93
91
|
#
|
94
92
|
def ruby(args, opts = {}, &block)
|
95
93
|
Exec._exec(self).ruby(args, Exec._setup_exec_opts(opts, self), &block)
|
@@ -150,7 +148,9 @@ module Toys
|
|
150
148
|
if opts[:exit_on_nonzero_status]
|
151
149
|
proc { |s| tool.exit(s.exitstatus) }
|
152
150
|
end
|
153
|
-
opts.merge(nonzero_status_handler: nonzero_status_handler)
|
151
|
+
opts = opts.merge(nonzero_status_handler: nonzero_status_handler)
|
152
|
+
opts.delete(:exit_on_nonzero_status)
|
153
|
+
opts
|
154
154
|
end
|
155
155
|
end
|
156
156
|
end
|
@@ -27,14 +27,13 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/utils/gems"
|
31
30
|
Toys::Utils::Gems.activate("highline", "~> 1.7")
|
32
31
|
require "highline"
|
33
32
|
|
34
33
|
module Toys
|
35
|
-
module
|
34
|
+
module StandardMixins
|
36
35
|
##
|
37
|
-
# A
|
36
|
+
# A mixin that provides access to the capabilities of the highline gem.
|
38
37
|
#
|
39
38
|
# You may make these methods available to your tool by including the
|
40
39
|
# following directive in your tool configuration:
|
@@ -27,12 +27,10 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
require "toys/utils/terminal"
|
31
|
-
|
32
30
|
module Toys
|
33
|
-
module
|
31
|
+
module StandardMixins
|
34
32
|
##
|
35
|
-
# A
|
33
|
+
# A mixin that provides a simple terminal. It includes a set of methods
|
36
34
|
# that produce styled output, get user input, and otherwise interact with
|
37
35
|
# the user's terminal.
|
38
36
|
#
|
data/lib/toys/tool.rb
CHANGED
@@ -39,6 +39,7 @@ module Toys
|
|
39
39
|
#
|
40
40
|
# Keys that are neither strings nor symbols are by convention used for other
|
41
41
|
# context information, including:
|
42
|
+
#
|
42
43
|
# * Common information such as the {Toys::Definition::Tool} object being
|
43
44
|
# executed, the arguments originally passed to it, or the usage error
|
44
45
|
# string. These well-known keys can be accessed via constants in the
|
@@ -46,7 +47,7 @@ module Toys
|
|
46
47
|
# * Common settings such as the verbosity level, and whether to exit
|
47
48
|
# immediately if a subprocess exits with a nonzero result. These keys are
|
48
49
|
# also present as {Toys::Context} constants.
|
49
|
-
# * Private information used internally by middleware and
|
50
|
+
# * Private information used internally by middleware and mixins.
|
50
51
|
#
|
51
52
|
# This class provides convenience accessors for common keys and settings, and
|
52
53
|
# you can retrieve argument-set keys using the {#options} hash.
|
@@ -246,7 +247,7 @@ module Toys
|
|
246
247
|
# Returns the subset of the context that uses string or symbol keys. By
|
247
248
|
# convention, this includes keys that are set by tool flags and arguments,
|
248
249
|
# but does not include well-known context values such as verbosity or
|
249
|
-
# private context values used by middleware or
|
250
|
+
# private context values used by middleware or mixins.
|
250
251
|
#
|
251
252
|
# @return [Hash]
|
252
253
|
#
|
data/lib/toys/utils/exec.rb
CHANGED
@@ -38,22 +38,6 @@ module Toys
|
|
38
38
|
# processes and their streams. It also provides shortcuts for common cases
|
39
39
|
# such as invoking Ruby in a subprocess or capturing output in a string.
|
40
40
|
#
|
41
|
-
# ## Stream handling
|
42
|
-
#
|
43
|
-
# By default, subprocess streams are connected to the corresponding streams
|
44
|
-
# in the parent process.
|
45
|
-
#
|
46
|
-
# Alternately, input streams may be read from a string you provide, and
|
47
|
-
# you may direct output streams to be captured and their contents exposed
|
48
|
-
# in the result object.
|
49
|
-
#
|
50
|
-
# You may also connect subprocess streams to a controller, which you can
|
51
|
-
# then manipulate by providing a block. Your block may read and write
|
52
|
-
# connected streams to interact with the process. For example, to redirect
|
53
|
-
# data into a subprocess you can connect its input stream to the controller
|
54
|
-
# using the `:in_from` option (see below). Then, in your block, you can
|
55
|
-
# write to that stream via the controller.
|
56
|
-
#
|
57
41
|
# ## Configuration options
|
58
42
|
#
|
59
43
|
# A variety of options can be used to control subprocesses. These include:
|
@@ -63,23 +47,12 @@ module Toys
|
|
63
47
|
# If not present, the command is not logged.
|
64
48
|
# * **:log_level** (Integer) Log level for logging the actual command.
|
65
49
|
# Defaults to Logger::INFO if not present.
|
66
|
-
# * **:
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# stream of the subprocess. If set to `:controller`, the controller
|
73
|
-
# will control the output stream. If set to `:capture`, the output will
|
74
|
-
# be captured in a string that is available in the
|
75
|
-
# {Toys::Utils::Exec::Result} object. If not set, the subprocess
|
76
|
-
# standard out is connected to STDOUT of the Toys process.
|
77
|
-
# * **:err_to** (`:controller`,`:capture`) Connects the standard error
|
78
|
-
# stream of the subprocess. If set to `:controller`, the controller
|
79
|
-
# will control the output stream. If set to `:capture`, the output will
|
80
|
-
# be captured in a string that is available in the
|
81
|
-
# {Toys::Utils::Exec::Result} object. If not set, the subprocess
|
82
|
-
# standard out is connected to STDERR of the Toys process.
|
50
|
+
# * **:in** Connects the input stream of the subprocess. See the section
|
51
|
+
# on stream handling.
|
52
|
+
# * **:out** Connects the standard output stream of the subprocess. See
|
53
|
+
# the section on stream handling.
|
54
|
+
# * **:err** Connects the standard error stream of the subprocess. See the
|
55
|
+
# section on stream handling.
|
83
56
|
#
|
84
57
|
# In addition, the following options recognized by `Process#spawn` are
|
85
58
|
# supported.
|
@@ -95,7 +68,57 @@ module Toys
|
|
95
68
|
#
|
96
69
|
# Configuration options may be provided to any method that starts a
|
97
70
|
# subprocess. You may also modify default values by calling
|
98
|
-
# {Toys::Utils::Exec#
|
71
|
+
# {Toys::Utils::Exec#configure_defaults}.
|
72
|
+
#
|
73
|
+
# ## Stream handling
|
74
|
+
#
|
75
|
+
# By default, subprocess streams are connected to the corresponding streams
|
76
|
+
# in the parent process. You can change this behavior, redirecting streams
|
77
|
+
# or providing ways to control them, using the `:in`, `:out`, and `:err`
|
78
|
+
# options.
|
79
|
+
#
|
80
|
+
# Three general strategies are available for custom stream handling. First,
|
81
|
+
# you may redirect to other streams such as files, IO objects, or Ruby
|
82
|
+
# strings. Some of these options map directly to options provided by the
|
83
|
+
# `Process#spawn` method. Second, you may use a controller to manipulate
|
84
|
+
# the streams programmatically. Third, you may capture output stream data
|
85
|
+
# and make it available in the result.
|
86
|
+
#
|
87
|
+
# Following is a full list of the stream handling options, along with how
|
88
|
+
# to specify them using the `:in`, `:out`, and `:err` options.
|
89
|
+
#
|
90
|
+
# * **Close the stream:** You may close the stream by passing `:close` as
|
91
|
+
# the option value. This is the same as passing `:close` to
|
92
|
+
# `Process#spawn`.
|
93
|
+
# * **Redirect to null:** You may redirect to a null stream by passing
|
94
|
+
# `:null` as the option value. This connects to a stream that is not
|
95
|
+
# closed but contains no data, i.e. `/dev/null` on unix systems.
|
96
|
+
# * **Redirect to a file:** You may redirect to a file. This reads from an
|
97
|
+
# existing file when connected to `:in`, and creates or appends to a
|
98
|
+
# file when connected to `:out` or `:err`. To specify a file, use the
|
99
|
+
# setting `[:file, "/path/to/file"]`. You may also, when writing a file,
|
100
|
+
# append an optional mode and permission code to the array. For
|
101
|
+
# example, `[:file, "/path/to/file", "a", 0644]`.
|
102
|
+
# * **Redirect to an IO object:** You may redirect to an IO object in the
|
103
|
+
# parent process, by passing the IO object as the option value. You may
|
104
|
+
# use any IO object. For example, you could connect the child's output
|
105
|
+
# to the parent's error using `out: $stderr`, or you could connect to an
|
106
|
+
# existing File stream. Unlike `Process#spawn`, this works for IO
|
107
|
+
# objects that do not have a corresponding file descriptor (such as
|
108
|
+
# StringIO objects). In such a case, a thread will be spawned to pipe
|
109
|
+
# the IO data through to the child process.
|
110
|
+
# * **Combine with another child stream:** You may redirect one child
|
111
|
+
# output stream to another, to combine them. To merge the child's error
|
112
|
+
# stream into its output stream, use `err: [:child, :out]`.
|
113
|
+
# * **Read from a string:** You may pass a string to the input stream by
|
114
|
+
# setting `[:string, "the string"]`. This works only for `:in`.
|
115
|
+
# * **Capture output stream:** You may capture a stream and make it
|
116
|
+
# available on the {Toys::Utils::Exec::Result} object, using the setting
|
117
|
+
# `:capture`. This works only for the `:out` and `:err` streams.
|
118
|
+
# * **Use the controller:** You may hook a stream to the controller using
|
119
|
+
# the setting `:controller`. You can then manipulate the stream via the
|
120
|
+
# controller. If you pass a block to {Toys::Utils::Exec#exec}, it yields
|
121
|
+
# the {Toys::Utils::Exec::Controller}, giving you access to streams.
|
99
122
|
#
|
100
123
|
class Exec
|
101
124
|
##
|
@@ -134,17 +157,17 @@ module Toys
|
|
134
157
|
# exit code and any captured output.
|
135
158
|
#
|
136
159
|
def exec(cmd, opts = {}, &block)
|
160
|
+
exec_opts = Opts.new(@default_opts).add(opts)
|
137
161
|
spawn_cmd =
|
138
162
|
if cmd.is_a?(::Array)
|
139
163
|
if cmd.size == 1 && cmd.first.is_a?(::String)
|
140
|
-
[[cmd.first,
|
164
|
+
[[cmd.first, exec_opts.config_opts[:argv0] || cmd.first]]
|
141
165
|
else
|
142
166
|
cmd
|
143
167
|
end
|
144
168
|
else
|
145
169
|
[cmd]
|
146
170
|
end
|
147
|
-
exec_opts = Opts.new(@default_opts).add(opts)
|
148
171
|
executor = Executor.new(exec_opts, spawn_cmd)
|
149
172
|
executor.execute(&block)
|
150
173
|
end
|
@@ -161,7 +184,7 @@ module Toys
|
|
161
184
|
# @yieldparam controller [Toys::Utils::Exec::Controller] A controller
|
162
185
|
# for the subprocess streams.
|
163
186
|
#
|
164
|
-
# @return [Toys::Utils::Result] The subprocess result, including
|
187
|
+
# @return [Toys::Utils::Exec::Result] The subprocess result, including
|
165
188
|
# exit code and any captured output.
|
166
189
|
#
|
167
190
|
def ruby(args, opts = {}, &block)
|
@@ -195,7 +218,7 @@ module Toys
|
|
195
218
|
# @return [String] What was written to standard out.
|
196
219
|
#
|
197
220
|
def capture(cmd, opts = {})
|
198
|
-
exec(cmd, opts.merge(
|
221
|
+
exec(cmd, opts.merge(out: :capture)).captured_out
|
199
222
|
end
|
200
223
|
|
201
224
|
##
|
@@ -208,13 +231,14 @@ module Toys
|
|
208
231
|
# @private
|
209
232
|
#
|
210
233
|
CONFIG_KEYS = %i[
|
234
|
+
argv0
|
211
235
|
env
|
212
|
-
|
213
|
-
|
236
|
+
err
|
237
|
+
in
|
214
238
|
logger
|
215
239
|
log_level
|
216
240
|
nonzero_status_handler
|
217
|
-
|
241
|
+
out
|
218
242
|
].freeze
|
219
243
|
|
220
244
|
##
|
@@ -247,8 +271,10 @@ module Toys
|
|
247
271
|
config.each do |k, v|
|
248
272
|
if CONFIG_KEYS.include?(k)
|
249
273
|
@config_opts[k] = v
|
250
|
-
elsif SPAWN_KEYS.include?(k)
|
274
|
+
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
251
275
|
@spawn_opts[k] = v
|
276
|
+
else
|
277
|
+
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
252
278
|
end
|
253
279
|
end
|
254
280
|
self
|
@@ -258,8 +284,10 @@ module Toys
|
|
258
284
|
keys.each do |k|
|
259
285
|
if CONFIG_KEYS.include?(k)
|
260
286
|
@config_opts.delete(k)
|
261
|
-
elsif SPAWN_KEYS.include?(k)
|
287
|
+
elsif SPAWN_KEYS.include?(k) || k.to_s.start_with?("rlimit_")
|
262
288
|
@spawn_opts.delete(k)
|
289
|
+
else
|
290
|
+
raise ::ArgumentError, "Unknown key: #{k.inspect}"
|
263
291
|
end
|
264
292
|
end
|
265
293
|
self
|
@@ -285,7 +313,7 @@ module Toys
|
|
285
313
|
|
286
314
|
##
|
287
315
|
# Return the subcommand's standard input stream (which can be written
|
288
|
-
# to), if the command was configured with `
|
316
|
+
# to), if the command was configured with `in: :controller`.
|
289
317
|
# Returns `nil` otherwise.
|
290
318
|
# @return [IO,nil]
|
291
319
|
#
|
@@ -293,7 +321,7 @@ module Toys
|
|
293
321
|
|
294
322
|
##
|
295
323
|
# Return the subcommand's standard output stream (which can be read
|
296
|
-
# from), if the command was configured with `
|
324
|
+
# from), if the command was configured with `out: :controller`.
|
297
325
|
# Returns `nil` otherwise.
|
298
326
|
# @return [IO,nil]
|
299
327
|
#
|
@@ -301,7 +329,7 @@ module Toys
|
|
301
329
|
|
302
330
|
##
|
303
331
|
# Return the subcommand's standard error stream (which can be read
|
304
|
-
# from), if the command was configured with `
|
332
|
+
# from), if the command was configured with `err: :controller`.
|
305
333
|
# Returns `nil` otherwise.
|
306
334
|
# @return [IO,nil]
|
307
335
|
#
|
@@ -337,14 +365,14 @@ module Toys
|
|
337
365
|
|
338
366
|
##
|
339
367
|
# Returns the captured output string, if the command was configured
|
340
|
-
# with `
|
368
|
+
# with `out: :capture`. Returns `nil` otherwise.
|
341
369
|
# @return [String,nil]
|
342
370
|
#
|
343
371
|
attr_reader :captured_out
|
344
372
|
|
345
373
|
##
|
346
374
|
# Returns the captured error string, if the command was configured
|
347
|
-
# with `
|
375
|
+
# with `err: :capture`. Returns `nil` otherwise.
|
348
376
|
# @return [String,nil]
|
349
377
|
#
|
350
378
|
attr_reader :captured_err
|
@@ -397,8 +425,8 @@ module Toys
|
|
397
425
|
|
398
426
|
def execute(&block)
|
399
427
|
setup_in_stream
|
400
|
-
setup_out_stream(:out
|
401
|
-
setup_out_stream(:err
|
428
|
+
setup_out_stream(:out)
|
429
|
+
setup_out_stream(:err)
|
402
430
|
log_command
|
403
431
|
wait_thread = start_process
|
404
432
|
status = control_process(wait_thread, &block)
|
@@ -447,54 +475,199 @@ module Toys
|
|
447
475
|
end
|
448
476
|
|
449
477
|
def setup_in_stream
|
450
|
-
setting = @config_opts[:
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
478
|
+
setting = @config_opts[:in]
|
479
|
+
return unless setting
|
480
|
+
case setting
|
481
|
+
when ::Symbol
|
482
|
+
setup_in_stream_of_type(setting, [])
|
483
|
+
when ::Integer
|
484
|
+
setup_in_stream_of_type(:parent, [setting])
|
485
|
+
when ::String
|
486
|
+
setup_in_stream_of_type(:file, [setting])
|
487
|
+
when ::IO, ::StringIO
|
488
|
+
interpret_in_io(setting)
|
489
|
+
when ::Array
|
490
|
+
interpret_in_array(setting)
|
491
|
+
else
|
492
|
+
raise "Unknown value for in: #{setting.inspect}"
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
def interpret_in_io(setting)
|
497
|
+
if setting.fileno.is_a?(::Integer)
|
498
|
+
setup_in_stream_of_type(:parent, [setting.fileno])
|
499
|
+
else
|
500
|
+
setup_in_stream_of_type(:copy_io, [setting])
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def interpret_in_array(setting)
|
505
|
+
case setting.first
|
506
|
+
when ::Symbol
|
507
|
+
setup_in_stream_of_type(setting.first, setting[1..-1])
|
508
|
+
when ::String
|
509
|
+
setup_in_stream_of_type(:file, setting)
|
510
|
+
else
|
511
|
+
raise "Unknown value for in: #{setting.inspect}"
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def setup_in_stream_of_type(type, args)
|
516
|
+
case type
|
517
|
+
when :controller
|
518
|
+
@controller_streams[:in] = make_in_pipe
|
519
|
+
when :null
|
520
|
+
make_null_stream(:in, "r")
|
521
|
+
when :close
|
522
|
+
@spawn_opts[:in] = type
|
523
|
+
when :parent
|
524
|
+
@spawn_opts[:in] = args.first
|
525
|
+
when :child
|
526
|
+
@spawn_opts[:in] = [:child, args.first]
|
527
|
+
when :string
|
528
|
+
write_string_thread(args.first.to_s)
|
529
|
+
when :copy_io
|
530
|
+
copy_to_in_thread(args.first)
|
531
|
+
when :file
|
532
|
+
interpret_in_file(args)
|
533
|
+
else
|
534
|
+
raise "Unknown type for in: #{type.inspect}"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def interpret_in_file(args)
|
539
|
+
raise "Expected only file name" unless args.size == 1 && args.first.is_a?(::String)
|
540
|
+
@spawn_opts[:in] = args + [::File::RDONLY]
|
541
|
+
end
|
542
|
+
|
543
|
+
def setup_out_stream(key)
|
544
|
+
setting = @config_opts[key]
|
545
|
+
return unless setting
|
546
|
+
case setting
|
547
|
+
when ::Symbol
|
548
|
+
setup_out_stream_of_type(key, setting, [])
|
549
|
+
when ::Integer
|
550
|
+
setup_out_stream_of_type(key, :parent, [setting])
|
551
|
+
when ::String
|
552
|
+
setup_out_stream_of_type(key, :file, [setting])
|
553
|
+
when ::IO, ::StringIO
|
554
|
+
interpret_out_io(key, setting)
|
555
|
+
when ::Array
|
556
|
+
interpret_out_array(key, setting)
|
557
|
+
else
|
558
|
+
raise "Unknown value for #{key}: #{setting.inspect}"
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def interpret_out_io(key, setting)
|
563
|
+
if setting.fileno.is_a?(::Integer)
|
564
|
+
setup_out_stream_of_type(key, :parent, [setting.fileno])
|
565
|
+
else
|
566
|
+
setup_out_stream_of_type(key, :copy_io, [setting])
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
def interpret_out_array(key, setting)
|
571
|
+
case setting.first
|
572
|
+
when ::Symbol
|
573
|
+
setup_out_stream_of_type(key, setting.first, setting[1..-1])
|
574
|
+
when ::String
|
575
|
+
setup_out_stream_of_type(key, :file, setting)
|
576
|
+
else
|
577
|
+
raise "Unknown value for #{key}: #{setting.inspect}"
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def setup_out_stream_of_type(key, type, args)
|
582
|
+
case type
|
583
|
+
when :controller
|
584
|
+
@controller_streams[key] = make_out_pipe(key)
|
585
|
+
when :null
|
586
|
+
make_null_stream(key, "w")
|
587
|
+
when :close, :out, :err
|
588
|
+
@spawn_opts[key] = type
|
589
|
+
when :parent
|
590
|
+
@spawn_opts[key] = args.first
|
591
|
+
when :child
|
592
|
+
@spawn_opts[key] = [:child, args.first]
|
593
|
+
when :capture
|
594
|
+
capture_stream_thread(key)
|
595
|
+
when :copy_io
|
596
|
+
copy_from_out_thread(key, args.first)
|
597
|
+
when :file
|
598
|
+
interpret_out_file(key, args)
|
599
|
+
else
|
600
|
+
raise "Unknown type for #{key}: #{type.inspect}"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def interpret_out_file(key, args)
|
605
|
+
raise "Expected file name" if args.empty? || !args.first.is_a?(::String)
|
606
|
+
raise "Too many file arguments" if args.size > 3
|
607
|
+
@spawn_opts[key] = args.size == 1 ? args.first : args
|
608
|
+
end
|
609
|
+
|
610
|
+
def make_null_stream(key, mode)
|
611
|
+
f = ::File.open(::File::NULL, mode)
|
612
|
+
@spawn_opts[key] = f
|
613
|
+
@child_streams << f
|
614
|
+
end
|
615
|
+
|
616
|
+
def make_in_pipe
|
617
|
+
r, w = ::IO.pipe
|
618
|
+
@spawn_opts[:in] = r
|
619
|
+
@child_streams << r
|
620
|
+
w.sync = true
|
621
|
+
w
|
622
|
+
end
|
623
|
+
|
624
|
+
def make_out_pipe(key)
|
625
|
+
r, w = ::IO.pipe
|
626
|
+
@spawn_opts[key] = w
|
627
|
+
@child_streams << w
|
628
|
+
r
|
629
|
+
end
|
630
|
+
|
631
|
+
def write_string_thread(string)
|
632
|
+
stream = make_in_pipe
|
633
|
+
@join_threads << ::Thread.new do
|
634
|
+
begin
|
635
|
+
stream.write string
|
636
|
+
ensure
|
637
|
+
stream.close
|
463
638
|
end
|
464
639
|
end
|
465
640
|
end
|
466
641
|
|
467
|
-
def
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
@controller_streams[stream_name] = r
|
476
|
-
when :capture
|
477
|
-
@join_threads << capture_stream_thread(r, stream_name)
|
478
|
-
else
|
479
|
-
raise "Unknown type for #{config_key}"
|
642
|
+
def copy_to_in_thread(io, close: false)
|
643
|
+
stream = make_in_pipe
|
644
|
+
@join_threads << ::Thread.new do
|
645
|
+
begin
|
646
|
+
::IO.copy_stream(io, stream)
|
647
|
+
ensure
|
648
|
+
stream.close
|
649
|
+
io.close if close
|
480
650
|
end
|
481
651
|
end
|
482
652
|
end
|
483
653
|
|
484
|
-
def
|
485
|
-
|
654
|
+
def copy_from_out_thread(key, io, close: false)
|
655
|
+
stream = make_out_pipe(key)
|
656
|
+
@join_threads << ::Thread.new do
|
486
657
|
begin
|
487
|
-
stream
|
658
|
+
::IO.copy_stream(stream, io)
|
488
659
|
ensure
|
489
660
|
stream.close
|
661
|
+
io.close if close
|
490
662
|
end
|
491
663
|
end
|
492
664
|
end
|
493
665
|
|
494
|
-
def capture_stream_thread(
|
495
|
-
|
666
|
+
def capture_stream_thread(key)
|
667
|
+
stream = make_out_pipe(key)
|
668
|
+
@join_threads << ::Thread.new do
|
496
669
|
begin
|
497
|
-
@captures[
|
670
|
+
@captures[key] = stream.read
|
498
671
|
ensure
|
499
672
|
stream.close
|
500
673
|
end
|