toys-core 0.3.7.1 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|