toys-core 0.20.0 → 0.21.0
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 +31 -0
- data/lib/toys/core.rb +1 -1
- data/lib/toys/settings.rb +37 -14
- data/lib/toys/standard_middleware/show_help.rb +8 -8
- data/lib/toys/standard_middleware/show_root_version.rb +1 -1
- data/lib/toys/standard_mixins/bundler.rb +8 -5
- data/lib/toys/standard_mixins/exec.rb +75 -38
- data/lib/toys/standard_mixins/xdg.rb +1 -1
- data/lib/toys/utils/exec.rb +216 -103
- data/lib/toys/utils/gems.rb +130 -89
- data/lib/toys/utils/git_cache.rb +170 -90
- data/lib/toys/utils/xdg.rb +143 -23
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2805bf71091b4ea8bf6f6a670281171dc6a5df4edec5196d80d64124ce2a199
|
|
4
|
+
data.tar.gz: b5c860449af3a0566e9b1fe8dc527cba4df6750ccc3b259365faf99fa79682fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95490a978d1a3ae04be668a2fd318de2499e9610bb6072a053776fb47214d2444c0d93a73fa690b46a6d4706e2fec1f20b76f8d111100ec65216f0c274efe77a
|
|
7
|
+
data.tar.gz: b9421ebfa989c45444024222cc3576a166d5797d7c982033265a11aa149c71ef0ab069441c70da490819e5bbafe95eb017c92289d6b46d7917c19634ee3eaf64
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### v0.21.0 / 2026-03-23
|
|
4
|
+
|
|
5
|
+
This release includes a variety of small fixes and updates toward improving product polish in preparation for a 1.0 release. It focuses on the following areas:
|
|
6
|
+
|
|
7
|
+
* Updates to the help system
|
|
8
|
+
* FIXED / BREAKING CHANGE: The help middleware omits the subtool filter flags on runnable tools that have subtools
|
|
9
|
+
* Updates to bundler integration
|
|
10
|
+
* FIXED: Fixed some cleanup issues when bundler setup fails
|
|
11
|
+
* Updates to the exec utility
|
|
12
|
+
* ADDED: Support for the `:unbundle` option which removes any existing bundle for a subprocess
|
|
13
|
+
* FIXED: Fixed several thread-safety issues with the controller
|
|
14
|
+
* FIXED: The result callback is now called in background mode if the result is never explicitly obtained from the controller
|
|
15
|
+
* Updates to the git_cache utility
|
|
16
|
+
* ADDED: Support for filtering lookups of ref and source info
|
|
17
|
+
* ADDED: Support for SHA-256 repos
|
|
18
|
+
* FIXED: Fixed failure to get a parent directory after previously getting a descendant object
|
|
19
|
+
* FIXED: GitCache no longer unnecessarily sets the write bit on files copied into a specified directory
|
|
20
|
+
* FIXED: GitCache no longer cleans out existing files when writing to a shared source, which should reduce issues with concurrent clients
|
|
21
|
+
* Updates to the XDG utility
|
|
22
|
+
* FIXED / BREAKING CHANGE: The XDG utility returns an empty array (instead of the system defaults) from the corresponding methods if `XDG_DATA_DIRS` or `XDG_CONFIG_DIRS` is nonempty but contains only relative paths
|
|
23
|
+
* ADDED: Provided `lookup_state` and `lookup_cache` methods on the XDG utility
|
|
24
|
+
* Updates to the settings system:
|
|
25
|
+
* FIXED: Block certain reserved names from being used as Settings field names
|
|
26
|
+
* FIXED: Fixed `Settings#load_data!` when subclassing another settings class
|
|
27
|
+
* FIXED: Reject non-String values in Settings regexp type spec
|
|
28
|
+
* FIXED: Settings guards against unknown classes when loading YAML on older Ruby versions
|
|
29
|
+
* FIXED: Settings guards against `ILLEGAL_VALUE` being passed to `range.member?`
|
|
30
|
+
* FIXED: Settings does a better job choosing exact-match values when using a union type
|
|
31
|
+
* FIXED: Settings reject non-numeric strings in Integer and Float converters
|
|
32
|
+
* FIXED: Settings propagates nested group errors in `Settings#load_data!`
|
|
33
|
+
|
|
3
34
|
### v0.20.0 / 2026-03-09
|
|
4
35
|
|
|
5
36
|
Toys-core 0.20 is a major release with several new features and a number of fixes, including a few minor breaking changes.
|
data/lib/toys/core.rb
CHANGED
data/lib/toys/settings.rb
CHANGED
|
@@ -35,8 +35,12 @@ module Toys
|
|
|
35
35
|
# Attribute names must start with an ascii letter, and may contain only ascii
|
|
36
36
|
# letters, digits, and underscores. Unlike method names, they may not include
|
|
37
37
|
# non-ascii unicode characters, nor may they end with `!` or `?`.
|
|
38
|
-
# Additionally,
|
|
39
|
-
#
|
|
38
|
+
# Additionally, some names are reserved because they would shadow critical
|
|
39
|
+
# Ruby methods or interfere with Settings' internal behavior (see
|
|
40
|
+
# {Toys::Settings::RESERVED_FIELD_NAMES} for the complete list, which
|
|
41
|
+
# currently includes names such as `class`, `clone`, `dup`, `freeze`, `hash`,
|
|
42
|
+
# `initialize`, `method_missing`, `object_id`, `public_send`, `raise`,
|
|
43
|
+
# `require`, and `send`).
|
|
40
44
|
#
|
|
41
45
|
# Each attribute defines four methods: a getter, a setter, an unsetter, and a
|
|
42
46
|
# set detector. In the above example, the attribute named `:endpoint` creates
|
|
@@ -297,12 +301,27 @@ module Toys
|
|
|
297
301
|
# grandchild_settings.str # => "value_from_root"
|
|
298
302
|
#
|
|
299
303
|
class Settings
|
|
304
|
+
##
|
|
300
305
|
# A special value indicating a type check failure.
|
|
306
|
+
#
|
|
301
307
|
ILLEGAL_VALUE = ::Object.new.freeze
|
|
302
308
|
|
|
309
|
+
##
|
|
303
310
|
# A special type specification indicating infer from the default value.
|
|
311
|
+
#
|
|
304
312
|
DEFAULT_TYPE = ::Object.new.freeze
|
|
305
313
|
|
|
314
|
+
##
|
|
315
|
+
# Field names that are not allowed because they would shadow critical Ruby
|
|
316
|
+
# methods or break Settings' own internal method calls.
|
|
317
|
+
#
|
|
318
|
+
# @return [Array<String>]
|
|
319
|
+
#
|
|
320
|
+
RESERVED_FIELD_NAMES = [
|
|
321
|
+
"class", "clone", "dup", "freeze", "hash", "initialize",
|
|
322
|
+
"method_missing", "object_id", "public_send", "raise", "require", "send"
|
|
323
|
+
].freeze
|
|
324
|
+
|
|
306
325
|
##
|
|
307
326
|
# Error raised when a value does not match the type constraint.
|
|
308
327
|
#
|
|
@@ -447,15 +466,14 @@ module Toys
|
|
|
447
466
|
range_class = (range.begin || range.end).class
|
|
448
467
|
new("(#{range})") do |val|
|
|
449
468
|
converted = convert(val, range_class)
|
|
450
|
-
range.member?(converted) ? converted : ILLEGAL_VALUE
|
|
469
|
+
converted != ILLEGAL_VALUE && range.member?(converted) ? converted : ILLEGAL_VALUE
|
|
451
470
|
end
|
|
452
471
|
end
|
|
453
472
|
|
|
454
473
|
def for_regexp(regexp)
|
|
455
474
|
regexp_str = regexp.source.gsub("/", "\\/")
|
|
456
475
|
new("/#{regexp_str}/") do |val|
|
|
457
|
-
|
|
458
|
-
regexp.match(str) ? str : ILLEGAL_VALUE
|
|
476
|
+
val.is_a?(::String) && regexp.match(val) ? val : ILLEGAL_VALUE
|
|
459
477
|
end
|
|
460
478
|
end
|
|
461
479
|
|
|
@@ -466,7 +484,7 @@ module Toys
|
|
|
466
484
|
result = ILLEGAL_VALUE
|
|
467
485
|
types.each do |type|
|
|
468
486
|
converted = type.call(val)
|
|
469
|
-
if converted
|
|
487
|
+
if converted.eql?(val)
|
|
470
488
|
result = val
|
|
471
489
|
break
|
|
472
490
|
elsif result == ILLEGAL_VALUE
|
|
@@ -518,7 +536,7 @@ module Toys
|
|
|
518
536
|
float_converter = proc do |val|
|
|
519
537
|
case val
|
|
520
538
|
when ::String
|
|
521
|
-
val
|
|
539
|
+
Float(val)
|
|
522
540
|
when ::Numeric
|
|
523
541
|
converted = val.to_f
|
|
524
542
|
converted == val ? converted : ILLEGAL_VALUE
|
|
@@ -530,7 +548,7 @@ module Toys
|
|
|
530
548
|
integer_converter = proc do |val|
|
|
531
549
|
case val
|
|
532
550
|
when ::String
|
|
533
|
-
val
|
|
551
|
+
Integer(val)
|
|
534
552
|
when ::Numeric
|
|
535
553
|
converted = val.to_i
|
|
536
554
|
converted == val ? converted : ILLEGAL_VALUE
|
|
@@ -605,7 +623,7 @@ module Toys
|
|
|
605
623
|
raise FieldError.new(value, self.class, name, nil) unless field
|
|
606
624
|
if field.group?
|
|
607
625
|
raise FieldError.new(value, self.class, name, "Hash") unless value.is_a?(::Hash)
|
|
608
|
-
get!(field).load_data!(value)
|
|
626
|
+
errors.concat(get!(field).load_data!(value, raise_on_failure: raise_on_failure))
|
|
609
627
|
else
|
|
610
628
|
set!(field, value)
|
|
611
629
|
end
|
|
@@ -628,7 +646,7 @@ module Toys
|
|
|
628
646
|
#
|
|
629
647
|
def load_yaml!(str, raise_on_failure: false)
|
|
630
648
|
require "psych"
|
|
631
|
-
load_data!(::Psych.
|
|
649
|
+
load_data!(::Psych.safe_load(str, permitted_classes: [::Symbol]), raise_on_failure: raise_on_failure)
|
|
632
650
|
end
|
|
633
651
|
|
|
634
652
|
##
|
|
@@ -641,7 +659,7 @@ module Toys
|
|
|
641
659
|
# @return [Array<FieldError>] An array of errors.
|
|
642
660
|
#
|
|
643
661
|
def load_yaml_file!(filename, raise_on_failure: false)
|
|
644
|
-
load_yaml!(File.read(filename), raise_on_failure: raise_on_failure)
|
|
662
|
+
load_yaml!(::File.read(filename), raise_on_failure: raise_on_failure)
|
|
645
663
|
end
|
|
646
664
|
|
|
647
665
|
##
|
|
@@ -668,7 +686,7 @@ module Toys
|
|
|
668
686
|
# @return [Array<FieldError>] An array of errors.
|
|
669
687
|
#
|
|
670
688
|
def load_json_file!(filename, raise_on_failure: false, **json_opts)
|
|
671
|
-
load_json!(File.read(filename), raise_on_failure: raise_on_failure, **json_opts)
|
|
689
|
+
load_json!(::File.read(filename), raise_on_failure: raise_on_failure, **json_opts)
|
|
672
690
|
end
|
|
673
691
|
|
|
674
692
|
##
|
|
@@ -848,7 +866,12 @@ module Toys
|
|
|
848
866
|
# all its instances.
|
|
849
867
|
#
|
|
850
868
|
def fields
|
|
851
|
-
@fields ||=
|
|
869
|
+
@fields ||=
|
|
870
|
+
if superclass.respond_to?(:fields)
|
|
871
|
+
superclass.fields.dup
|
|
872
|
+
else
|
|
873
|
+
{}
|
|
874
|
+
end
|
|
852
875
|
end
|
|
853
876
|
|
|
854
877
|
##
|
|
@@ -882,7 +905,7 @@ module Toys
|
|
|
882
905
|
|
|
883
906
|
def interpret_name(name)
|
|
884
907
|
name = name.to_s
|
|
885
|
-
if name !~ /^[a-zA-Z]\w*$/ || name
|
|
908
|
+
if name !~ /^[a-zA-Z]\w*$/ || RESERVED_FIELD_NAMES.include?(name)
|
|
886
909
|
raise ::ArgumentError, "Illegal settings field name: #{name}"
|
|
887
910
|
end
|
|
888
911
|
existing = public_instance_methods(false)
|
|
@@ -53,43 +53,43 @@ module Toys
|
|
|
53
53
|
# Key set when the show help flag is present
|
|
54
54
|
# @return [Object]
|
|
55
55
|
#
|
|
56
|
-
SHOW_HELP_KEY = Object.new.freeze
|
|
56
|
+
SHOW_HELP_KEY = ::Object.new.freeze
|
|
57
57
|
|
|
58
58
|
##
|
|
59
59
|
# Key set when the show usage flag is present
|
|
60
60
|
# @return [Object]
|
|
61
61
|
#
|
|
62
|
-
SHOW_USAGE_KEY = Object.new.freeze
|
|
62
|
+
SHOW_USAGE_KEY = ::Object.new.freeze
|
|
63
63
|
|
|
64
64
|
##
|
|
65
65
|
# Key set when the show subtool list flag is present
|
|
66
66
|
# @return [Object]
|
|
67
67
|
#
|
|
68
|
-
SHOW_LIST_KEY = Object.new.freeze
|
|
68
|
+
SHOW_LIST_KEY = ::Object.new.freeze
|
|
69
69
|
|
|
70
70
|
##
|
|
71
71
|
# Key for the recursive setting
|
|
72
72
|
# @return [Object]
|
|
73
73
|
#
|
|
74
|
-
RECURSIVE_SUBTOOLS_KEY = Object.new.freeze
|
|
74
|
+
RECURSIVE_SUBTOOLS_KEY = ::Object.new.freeze
|
|
75
75
|
|
|
76
76
|
##
|
|
77
77
|
# Key for the search string
|
|
78
78
|
# @return [Object]
|
|
79
79
|
#
|
|
80
|
-
SEARCH_STRING_KEY = Object.new.freeze
|
|
80
|
+
SEARCH_STRING_KEY = ::Object.new.freeze
|
|
81
81
|
|
|
82
82
|
##
|
|
83
83
|
# Key for the show-all-subtools setting
|
|
84
84
|
# @return [Object]
|
|
85
85
|
#
|
|
86
|
-
SHOW_ALL_SUBTOOLS_KEY = Object.new.freeze
|
|
86
|
+
SHOW_ALL_SUBTOOLS_KEY = ::Object.new.freeze
|
|
87
87
|
|
|
88
88
|
##
|
|
89
89
|
# Key for the tool name
|
|
90
90
|
# @return [Object]
|
|
91
91
|
#
|
|
92
|
-
TOOL_NAME_KEY = Object.new.freeze
|
|
92
|
+
TOOL_NAME_KEY = ::Object.new.freeze
|
|
93
93
|
|
|
94
94
|
##
|
|
95
95
|
# Create a ShowHelp middleware.
|
|
@@ -214,7 +214,7 @@ module Toys
|
|
|
214
214
|
list_flags = has_subtools ? add_list_flags(tool) : []
|
|
215
215
|
can_display_help = !help_flags.empty? || !list_flags.empty? ||
|
|
216
216
|
!usage_flags.empty? || @fallback_execution
|
|
217
|
-
if can_display_help && has_subtools
|
|
217
|
+
if can_display_help && has_subtools && !tool.runnable?
|
|
218
218
|
add_recursive_flags(tool)
|
|
219
219
|
add_search_flags(tool)
|
|
220
220
|
add_show_all_subtools_flags(tool)
|
|
@@ -23,7 +23,8 @@ module Toys
|
|
|
23
23
|
# The following parameters can be passed when including this mixin:
|
|
24
24
|
#
|
|
25
25
|
# * `:static` (Boolean) Has the same effect as passing `:static` to the
|
|
26
|
-
# `:setup` parameter.
|
|
26
|
+
# `:setup` parameter. This is present largely for historical
|
|
27
|
+
# compatibility, but it is supported and _not_ deprecated.
|
|
27
28
|
#
|
|
28
29
|
# * `:setup` (:auto,:manual,:static) A symbol indicating when the bundle
|
|
29
30
|
# should be installed. Possible values are:
|
|
@@ -114,7 +115,7 @@ module Toys
|
|
|
114
115
|
# will be a hash of parameters if the bundle has not been set up yet, or
|
|
115
116
|
# nil if the bundle has already been set up.
|
|
116
117
|
#
|
|
117
|
-
SETUP_PARAMS_KEY = Object.new.freeze
|
|
118
|
+
SETUP_PARAMS_KEY = ::Object.new.freeze
|
|
118
119
|
|
|
119
120
|
##
|
|
120
121
|
# @private
|
|
@@ -152,6 +153,8 @@ module Toys
|
|
|
152
153
|
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
|
153
154
|
when :manual
|
|
154
155
|
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = kwargs
|
|
156
|
+
when :static
|
|
157
|
+
# Already set up at include time
|
|
155
158
|
end
|
|
156
159
|
end
|
|
157
160
|
|
|
@@ -161,7 +164,7 @@ module Toys
|
|
|
161
164
|
when :static
|
|
162
165
|
::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
|
|
163
166
|
when :manual
|
|
164
|
-
# @private
|
|
167
|
+
# @private Defined dynamically for the tool but not visible to YARD
|
|
165
168
|
def bundler_setup(**kwargs)
|
|
166
169
|
original_kwargs = self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY]
|
|
167
170
|
raise ::Toys::Utils::Gems::AlreadyBundledError unless original_kwargs
|
|
@@ -172,7 +175,7 @@ module Toys
|
|
|
172
175
|
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY] = nil
|
|
173
176
|
end
|
|
174
177
|
|
|
175
|
-
# @private
|
|
178
|
+
# @private Defined dynamically for the tool but not visible to YARD
|
|
176
179
|
def bundler_setup?
|
|
177
180
|
self[::Toys::StandardMixins::Bundler::SETUP_PARAMS_KEY].nil?
|
|
178
181
|
end
|
|
@@ -193,7 +196,7 @@ module Toys
|
|
|
193
196
|
def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
|
|
194
197
|
@context_directory = context_directory
|
|
195
198
|
@source_info = source_info
|
|
196
|
-
@gemfile_names = gemfile_names
|
|
199
|
+
@gemfile_names = gemfile_names || ::Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES
|
|
197
200
|
@toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
|
|
198
201
|
end
|
|
199
202
|
|
|
@@ -35,36 +35,6 @@ module Toys
|
|
|
35
35
|
# but you can also retrieve the service object itself by calling
|
|
36
36
|
# {Toys::Context#get} with the key {Toys::StandardMixins::Exec::KEY}.
|
|
37
37
|
#
|
|
38
|
-
# ### Controlling processes
|
|
39
|
-
#
|
|
40
|
-
# A process can be started in the *foreground* or the *background*. If you
|
|
41
|
-
# start a foreground process, it will "take over" your standard input and
|
|
42
|
-
# output streams by default, and it will keep control until it completes.
|
|
43
|
-
# If you start a background process, its streams will be redirected to null
|
|
44
|
-
# by default, and control will be returned to you immediately.
|
|
45
|
-
#
|
|
46
|
-
# While a process is running, you can control it using a
|
|
47
|
-
# {Toys::Utils::Exec::Controller} object. Use a controller to interact with
|
|
48
|
-
# the process's input and output streams, send it signals, or wait for it
|
|
49
|
-
# to complete.
|
|
50
|
-
#
|
|
51
|
-
# When running a process in the foreground, the controller will be yielded
|
|
52
|
-
# to an optional block. For example, the following code starts a process in
|
|
53
|
-
# the foreground and passes its output stream to a controller.
|
|
54
|
-
#
|
|
55
|
-
# exec(["git", "init"], out: :controller) do |controller|
|
|
56
|
-
# loop do
|
|
57
|
-
# line = controller.out.gets
|
|
58
|
-
# break if line.nil?
|
|
59
|
-
# puts "Got line: #{line}"
|
|
60
|
-
# end
|
|
61
|
-
# end
|
|
62
|
-
#
|
|
63
|
-
# When running a process in the background, the controller is returned from
|
|
64
|
-
# the method that starts the process:
|
|
65
|
-
#
|
|
66
|
-
# controller = exec(["git", "init"], background: true)
|
|
67
|
-
#
|
|
68
38
|
# ### Stream handling
|
|
69
39
|
#
|
|
70
40
|
# By default, subprocess streams are connected to the corresponding streams
|
|
@@ -84,7 +54,7 @@ module Toys
|
|
|
84
54
|
#
|
|
85
55
|
# * **Inherit parent stream:** You can inherit the corresponding stream
|
|
86
56
|
# in the parent process by passing `:inherit` as the option value. This
|
|
87
|
-
# is the default if the subprocess is
|
|
57
|
+
# is the default if the subprocess is run in the foreground.
|
|
88
58
|
#
|
|
89
59
|
# * **Redirect to null:** You can redirect to a null stream by passing
|
|
90
60
|
# `:null` as the option value. This connects to a stream that is not
|
|
@@ -136,7 +106,7 @@ module Toys
|
|
|
136
106
|
# the setting `:controller`. You can then manipulate the stream via the
|
|
137
107
|
# controller. If you pass a block to {Toys::StandardMixins::Exec#exec},
|
|
138
108
|
# it yields the {Toys::Utils::Exec::Controller}, giving you access to
|
|
139
|
-
# streams.
|
|
109
|
+
# streams. See the section below on controlling processes.
|
|
140
110
|
#
|
|
141
111
|
# * **Make copies of an output stream:** You can "tee," or duplicate the
|
|
142
112
|
# `:out` or `:err` stream and redirect those copies to various
|
|
@@ -156,6 +126,68 @@ module Toys
|
|
|
156
126
|
# the tee. Larger buffers may allow higher throughput. The default
|
|
157
127
|
# is 65536.
|
|
158
128
|
#
|
|
129
|
+
# ### Controlling processes
|
|
130
|
+
#
|
|
131
|
+
# A process can be started in the *foreground* or the *background*. If you
|
|
132
|
+
# start a foreground process, it will inherit your standard input and
|
|
133
|
+
# output streams by default, and it will keep control until it completes.
|
|
134
|
+
# If you start a background process, its streams will be redirected to null
|
|
135
|
+
# by default, and control will be returned to you immediately.
|
|
136
|
+
#
|
|
137
|
+
# While a process is running, you can control it using a
|
|
138
|
+
# {Toys::Utils::Exec::Controller} object. Use a controller to interact with
|
|
139
|
+
# the process's input and output streams, send it signals, or wait for it
|
|
140
|
+
# to complete.
|
|
141
|
+
#
|
|
142
|
+
# When running a process in the foreground, the controller will be yielded
|
|
143
|
+
# to an optional block. For example, the following code starts a process in
|
|
144
|
+
# the foreground and passes its output stream to a controller.
|
|
145
|
+
#
|
|
146
|
+
# exec(["git", "init"], out: :controller) do |controller|
|
|
147
|
+
# loop do
|
|
148
|
+
# line = controller.out.gets
|
|
149
|
+
# break if line.nil?
|
|
150
|
+
# puts "Got line: #{line}"
|
|
151
|
+
# end
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# At the end of the block, if the controller is handling the process's
|
|
155
|
+
# input stream, that stream will automatically be closed. The following
|
|
156
|
+
# example programmatically sends data to the `wc` unix program, and
|
|
157
|
+
# captures its output. Because the controller is handling the input stream,
|
|
158
|
+
# it automatically closes the stream at the end of the block, which causes
|
|
159
|
+
# `wc` to end.
|
|
160
|
+
#
|
|
161
|
+
# result = exec(["wc"], in: :controller, out: :capture) do |controller|
|
|
162
|
+
# controller.in.puts "Hello, world!"
|
|
163
|
+
# end
|
|
164
|
+
# puts "Results: #{result.captured_out}"
|
|
165
|
+
#
|
|
166
|
+
# Otherwise, depending on the process's behavior, it may continue to run
|
|
167
|
+
# after the end of the block. Control will not be returned to the caller
|
|
168
|
+
# until the process actually terminates. Conversely, it is also possible
|
|
169
|
+
# the process could terminate by itself while the block is still executing.
|
|
170
|
+
# You can call controller methods to obtain the process's actual current
|
|
171
|
+
# state.
|
|
172
|
+
#
|
|
173
|
+
# When running a process in the background, the controller is returned
|
|
174
|
+
# immediately from the method that starts the process. In the following
|
|
175
|
+
# example, git init is kicked off in the background and the output is
|
|
176
|
+
# thrown away to /dev/null.
|
|
177
|
+
#
|
|
178
|
+
# controller = exec(["git", "init"], background: true)
|
|
179
|
+
#
|
|
180
|
+
# In this mode, use the returned controller to query the process's state
|
|
181
|
+
# and interact with it. Streams directed to the controller are not
|
|
182
|
+
# automatically closed, so you will need to do so yourself. Following is an
|
|
183
|
+
# example of running `wc` in the background:
|
|
184
|
+
#
|
|
185
|
+
# controller = exec(["wc"], background: true,
|
|
186
|
+
# in: :controller, out: :controller)
|
|
187
|
+
# controller.in.puts "Hello, world!"
|
|
188
|
+
# controller.in.close # Do this explicitly to cause wc to finish
|
|
189
|
+
# puts "Results: #{controller.out.read}" # Read the entire stream
|
|
190
|
+
#
|
|
159
191
|
# ### Result handling
|
|
160
192
|
#
|
|
161
193
|
# A subprocess result is represented by a {Toys::Utils::Exec::Result}
|
|
@@ -193,6 +225,12 @@ module Toys
|
|
|
193
225
|
# puts "exit code: #{result.exit_code}"
|
|
194
226
|
# end
|
|
195
227
|
#
|
|
228
|
+
# In foreground mode, the callback is executed in the calling thread, after
|
|
229
|
+
# the process terminates (and after any controller block has completed) but
|
|
230
|
+
# before control is returned to the caller. In background mode, the
|
|
231
|
+
# callback is executed asynchronously in a separate thread after the
|
|
232
|
+
# process terminates.
|
|
233
|
+
#
|
|
196
234
|
# Finally, you can force your tool to exit if a subprocess fails, similar
|
|
197
235
|
# to setting the `set -e` option in bash, by setting the
|
|
198
236
|
# `:exit_on_nonzero_status` option. This is often set as a default
|
|
@@ -216,6 +254,10 @@ module Toys
|
|
|
216
254
|
#
|
|
217
255
|
# * `:background` (Boolean) Runs the process in the background if `true`.
|
|
218
256
|
#
|
|
257
|
+
# * `:unbundle` (Boolean) Disables any existing bundle when running the
|
|
258
|
+
# subprocess. Has no effect if Bundler isn't active at the call point.
|
|
259
|
+
# Cannot be used when executing in a fork, e.g. via {#exec_proc}.
|
|
260
|
+
#
|
|
219
261
|
# Options related to handling results
|
|
220
262
|
#
|
|
221
263
|
# * `:result_callback` (Proc,Symbol) A procedure that is called, and
|
|
@@ -837,12 +879,7 @@ module Toys
|
|
|
837
879
|
context = self
|
|
838
880
|
opts = Exec._setup_exec_opts(opts, context)
|
|
839
881
|
context[KEY] = Utils::Exec.new(**opts) do |k|
|
|
840
|
-
|
|
841
|
-
when :logger
|
|
842
|
-
context[Context::Key::LOGGER]
|
|
843
|
-
when :cli
|
|
844
|
-
context[Context::Key::CLI]
|
|
845
|
-
end
|
|
882
|
+
k == :logger ? context[Context::Key::LOGGER] : nil
|
|
846
883
|
end
|
|
847
884
|
end
|
|
848
885
|
end
|
|
@@ -10,7 +10,7 @@ module Toys
|
|
|
10
10
|
# utility methods that locate base directories and search paths for
|
|
11
11
|
# application state, configuration, caches, and other data, according to
|
|
12
12
|
# the [XDG Base Directory Spec version
|
|
13
|
-
# 0.8](https://specifications.freedesktop.org/basedir
|
|
13
|
+
# 0.8](https://specifications.freedesktop.org/basedir/0.8/).
|
|
14
14
|
#
|
|
15
15
|
# @example
|
|
16
16
|
#
|