toys-core 0.9.4 → 0.10.4
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/.yardopts +2 -1
- data/CHANGELOG.md +47 -0
- data/LICENSE.md +1 -1
- data/README.md +3 -3
- data/lib/toys-core.rb +14 -21
- data/lib/toys/acceptor.rb +0 -21
- data/lib/toys/arg_parser.rb +1 -22
- data/lib/toys/cli.rb +102 -70
- data/lib/toys/compat.rb +54 -41
- data/lib/toys/completion.rb +0 -21
- data/lib/toys/context.rb +0 -23
- data/lib/toys/core.rb +1 -22
- data/lib/toys/dsl/flag.rb +0 -21
- data/lib/toys/dsl/flag_group.rb +0 -21
- data/lib/toys/dsl/positional_arg.rb +0 -21
- data/lib/toys/dsl/tool.rb +135 -51
- data/lib/toys/errors.rb +0 -21
- data/lib/toys/flag.rb +0 -21
- data/lib/toys/flag_group.rb +0 -21
- data/lib/toys/input_file.rb +0 -21
- data/lib/toys/loader.rb +41 -78
- data/lib/toys/middleware.rb +146 -77
- data/lib/toys/mixin.rb +0 -21
- data/lib/toys/module_lookup.rb +3 -26
- data/lib/toys/positional_arg.rb +0 -21
- data/lib/toys/source_info.rb +49 -38
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +0 -23
- data/lib/toys/standard_middleware/apply_config.rb +42 -0
- data/lib/toys/standard_middleware/handle_usage_errors.rb +7 -28
- data/lib/toys/standard_middleware/set_default_descriptions.rb +0 -23
- data/lib/toys/standard_middleware/show_help.rb +0 -23
- data/lib/toys/standard_middleware/show_root_version.rb +0 -23
- data/lib/toys/standard_mixins/bundler.rb +113 -0
- data/lib/toys/standard_mixins/exec.rb +478 -128
- data/lib/toys/standard_mixins/fileutils.rb +0 -21
- data/lib/toys/standard_mixins/gems.rb +2 -24
- data/lib/toys/standard_mixins/highline.rb +0 -21
- data/lib/toys/standard_mixins/terminal.rb +0 -21
- data/lib/toys/template.rb +0 -21
- data/lib/toys/tool.rb +22 -34
- data/lib/toys/utils/completion_engine.rb +0 -21
- data/lib/toys/utils/exec.rb +142 -71
- data/lib/toys/utils/gems.rb +221 -67
- data/lib/toys/utils/gems/gemfile.rb +6 -0
- data/lib/toys/utils/help_text.rb +0 -21
- data/lib/toys/utils/terminal.rb +47 -38
- data/lib/toys/wrappable_string.rb +0 -21
- metadata +7 -116
data/lib/toys/utils/gems.rb
CHANGED
@@ -1,30 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
3
|
+
require "monitor"
|
4
|
+
require "rubygems"
|
23
5
|
|
24
6
|
module Toys
|
25
7
|
module Utils
|
26
8
|
##
|
27
|
-
# A helper
|
9
|
+
# A helper class that activates and installs gems and sets up bundler.
|
28
10
|
#
|
29
11
|
# This class is not loaded by default. Before using it directly, you should
|
30
12
|
# `require "toys/utils/gems"`
|
@@ -58,6 +40,30 @@ module Toys
|
|
58
40
|
end
|
59
41
|
end
|
60
42
|
|
43
|
+
##
|
44
|
+
# Failed to run Bundler
|
45
|
+
#
|
46
|
+
class BundlerFailedError < ::StandardError
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Could not find a Gemfile
|
51
|
+
#
|
52
|
+
class GemfileNotFoundError < BundlerFailedError
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# The bundle is not and could not be installed
|
57
|
+
#
|
58
|
+
class BundleNotInstalledError < BundlerFailedError
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Bundler has already been run; cannot do so again
|
63
|
+
#
|
64
|
+
class AlreadyBundledError < BundlerFailedError
|
65
|
+
end
|
66
|
+
|
61
67
|
##
|
62
68
|
# Activate the given gem. If it is not present, attempt to install it (or
|
63
69
|
# inform the user to update the bundle).
|
@@ -73,24 +79,48 @@ module Toys
|
|
73
79
|
##
|
74
80
|
# Create a new gem activator.
|
75
81
|
#
|
76
|
-
# @param
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
# @param on_missing [:confirm,:error,:install] What to do if a needed gem
|
83
|
+
# is not installed. Possible values:
|
84
|
+
#
|
85
|
+
# * `:confirm` - prompt the user on whether to install
|
86
|
+
# * `:error` - raise an exception
|
87
|
+
# * `:install` - just install the gem
|
88
|
+
#
|
89
|
+
# The default is `:confirm`.
|
90
|
+
#
|
91
|
+
# @param on_conflict [:error,:warn,:ignore] What to do if bundler has
|
92
|
+
# already been run with a different Gemfile. Possible values:
|
93
|
+
#
|
94
|
+
# * `:error` - raise an exception
|
95
|
+
# * `:ignore` - just silently proceed without bundling again
|
96
|
+
# * `:warn` - print a warning and proceed without bundling again
|
97
|
+
#
|
98
|
+
# The default is `:error`.
|
99
|
+
#
|
100
|
+
# @param terminal [Toys::Utils::Terminal] Terminal to use (optional)
|
101
|
+
# @param input [IO] Input IO (optional, defaults to STDIN)
|
102
|
+
# @param output [IO] Output IO (optional, defaults to STDOUT)
|
103
|
+
# @param suppress_confirm [Boolean] Deprecated. Use `on_missing` instead.
|
104
|
+
# @param default_confirm [Boolean] Deprecated. Use `on_missing` instead.
|
105
|
+
#
|
106
|
+
def initialize(on_missing: nil,
|
107
|
+
on_conflict: nil,
|
108
|
+
terminal: nil,
|
109
|
+
input: nil,
|
110
|
+
output: nil,
|
111
|
+
suppress_confirm: nil,
|
112
|
+
default_confirm: nil)
|
113
|
+
@default_confirm = default_confirm || default_confirm.nil? ? true : false
|
114
|
+
@on_missing = on_missing ||
|
115
|
+
if suppress_confirm
|
116
|
+
@default_confirm ? :install : :error
|
117
|
+
else
|
118
|
+
:confirm
|
119
|
+
end
|
120
|
+
@on_conflict = on_conflict || :error
|
121
|
+
@terminal = terminal
|
122
|
+
@input = input || ::STDIN
|
123
|
+
@output = output || ::STDOUT
|
94
124
|
end
|
95
125
|
|
96
126
|
##
|
@@ -102,13 +132,57 @@ module Toys
|
|
102
132
|
# @return [void]
|
103
133
|
#
|
104
134
|
def activate(name, *requirements)
|
105
|
-
|
106
|
-
|
107
|
-
|
135
|
+
Gems.synchronize do
|
136
|
+
begin
|
137
|
+
gem(name, *requirements)
|
138
|
+
rescue ::Gem::LoadError => e
|
139
|
+
handle_activation_error(e, name, requirements)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Set up the bundle.
|
146
|
+
#
|
147
|
+
# @param groups [Array<String>] The groups to include in setup
|
148
|
+
# @param search_dirs [Array<String>] Directories to search for a Gemfile
|
149
|
+
# @return [void]
|
150
|
+
#
|
151
|
+
def bundle(groups: nil,
|
152
|
+
search_dirs: nil)
|
153
|
+
Gems.synchronize do
|
154
|
+
gemfile_path = find_gemfile(Array(search_dirs))
|
155
|
+
if configure_gemfile(gemfile_path)
|
156
|
+
activate("bundler", "~> 2.1")
|
157
|
+
require "bundler"
|
158
|
+
setup_bundle(gemfile_path, groups || [])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
@global_mutex = ::Monitor.new
|
164
|
+
|
165
|
+
## @private
|
166
|
+
def self.synchronize(&block)
|
167
|
+
@global_mutex.synchronize(&block)
|
108
168
|
end
|
109
169
|
|
110
170
|
private
|
111
171
|
|
172
|
+
def terminal
|
173
|
+
@terminal ||= begin
|
174
|
+
require "toys/utils/terminal"
|
175
|
+
Utils::Terminal.new(input: @input, output: @output)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def exec_util
|
180
|
+
@exec_util ||= begin
|
181
|
+
require "toys/utils/exec"
|
182
|
+
Utils::Exec.new
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
112
186
|
def handle_activation_error(error, name, requirements)
|
113
187
|
is_missing_spec =
|
114
188
|
if defined?(::Gem::MissingSpecError)
|
@@ -116,15 +190,15 @@ module Toys
|
|
116
190
|
else
|
117
191
|
error.message.include?("Could not find")
|
118
192
|
end
|
119
|
-
|
120
|
-
|
193
|
+
if !is_missing_spec || @on_missing == :error
|
194
|
+
report_activation_error(name, requirements, error)
|
121
195
|
return
|
122
196
|
end
|
123
|
-
|
197
|
+
confirm_and_install_gem(name, requirements)
|
124
198
|
begin
|
125
199
|
gem(name, *requirements)
|
126
200
|
rescue ::Gem::LoadError => e
|
127
|
-
|
201
|
+
report_activation_error(name, requirements, e)
|
128
202
|
end
|
129
203
|
end
|
130
204
|
|
@@ -132,41 +206,121 @@ module Toys
|
|
132
206
|
"#{name.inspect}, #{requirements.map(&:inspect).join(', ')}"
|
133
207
|
end
|
134
208
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
default: @default_confirm)
|
209
|
+
def confirm_and_install_gem(name, requirements)
|
210
|
+
if @on_missing == :confirm
|
211
|
+
requirements_text = gem_requirements_text(name, requirements)
|
212
|
+
response = terminal.confirm("Gem needed: #{requirements_text}. Install? ",
|
213
|
+
default: @default_confirm)
|
214
|
+
unless response
|
215
|
+
raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
|
143
216
|
end
|
144
|
-
unless response
|
145
|
-
raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
|
146
|
-
end
|
147
|
-
perform_install(name, requirements)
|
148
|
-
end
|
149
|
-
|
150
|
-
def perform_install(name, requirements)
|
151
|
-
result = @terminal.spinner(leading_text: "Installing gem #{name}... ",
|
152
|
-
final_text: "Done.\n") do
|
153
|
-
@exec.exec(["gem", "install", name, "--version", requirements.join(",")],
|
154
|
-
out: :capture, err: :capture)
|
155
217
|
end
|
156
|
-
|
218
|
+
result = exec_util.exec(["gem", "install", name, "--version", requirements.join(",")])
|
157
219
|
if result.error?
|
158
220
|
raise InstallFailedError, "Failed to install gem #{name}"
|
159
221
|
end
|
160
222
|
::Gem::Specification.reset
|
161
223
|
end
|
162
224
|
|
163
|
-
def
|
225
|
+
def report_activation_error(name, requirements, err)
|
164
226
|
if ::ENV["BUNDLE_GEMFILE"]
|
165
227
|
raise GemfileUpdateNeededError.new(gem_requirements_text(name, requirements),
|
166
228
|
::ENV["BUNDLE_GEMFILE"])
|
167
229
|
end
|
168
230
|
raise ActivationFailedError, err.message
|
169
231
|
end
|
232
|
+
|
233
|
+
def find_gemfile(search_dirs)
|
234
|
+
search_dirs.each do |dir|
|
235
|
+
gemfile_path = ::File.join(dir, "Gemfile")
|
236
|
+
return gemfile_path if ::File.readable?(gemfile_path)
|
237
|
+
end
|
238
|
+
raise GemfileNotFoundError, "Gemfile not found"
|
239
|
+
end
|
240
|
+
|
241
|
+
def configure_gemfile(gemfile_path)
|
242
|
+
old_path = ::ENV["BUNDLE_GEMFILE"]
|
243
|
+
if old_path
|
244
|
+
if gemfile_path != old_path
|
245
|
+
case @on_conflict
|
246
|
+
when :warn
|
247
|
+
terminal.puts("Warning: could not set up bundler because it is already set up.", :red)
|
248
|
+
when :error
|
249
|
+
raise AlreadyBundledError, "Could not set up bundler because it is already set up"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
return false
|
253
|
+
end
|
254
|
+
::ENV["BUNDLE_GEMFILE"] = gemfile_path
|
255
|
+
true
|
256
|
+
end
|
257
|
+
|
258
|
+
def setup_bundle(gemfile_path, groups)
|
259
|
+
begin
|
260
|
+
modify_bundle_definition(gemfile_path)
|
261
|
+
::Bundler.setup(*groups)
|
262
|
+
rescue ::Bundler::GemNotFound
|
263
|
+
restore_toys_libs
|
264
|
+
install_bundle(gemfile_path)
|
265
|
+
::Bundler.reset!
|
266
|
+
modify_bundle_definition(gemfile_path)
|
267
|
+
::Bundler.setup(*groups)
|
268
|
+
end
|
269
|
+
restore_toys_libs
|
270
|
+
end
|
271
|
+
|
272
|
+
def modify_bundle_definition(gemfile_path)
|
273
|
+
builder = ::Bundler::Dsl.new
|
274
|
+
builder.eval_gemfile(gemfile_path)
|
275
|
+
begin
|
276
|
+
builder.eval_gemfile(::File.join(__dir__, "gems", "gemfile.rb"))
|
277
|
+
rescue ::Bundler::Dsl::DSLError
|
278
|
+
terminal.puts(
|
279
|
+
"WARNING: Unable to integrate your Gemfile into the Toys runtime.\n" \
|
280
|
+
"When using the Toys Bundler integration features, do NOT list\n" \
|
281
|
+
"the toys or toys-core gems directly in your Gemfile. They can be\n" \
|
282
|
+
"dependencies of another gem, but cannot be listed directly.",
|
283
|
+
:red
|
284
|
+
)
|
285
|
+
return
|
286
|
+
end
|
287
|
+
toys_gems = ["toys-core"]
|
288
|
+
toys_gems << "toys" if ::Toys.const_defined?(:VERSION)
|
289
|
+
definition = builder.to_definition(gemfile_path + ".lock", { gems: toys_gems })
|
290
|
+
::Bundler.instance_variable_set(:@definition, definition)
|
291
|
+
end
|
292
|
+
|
293
|
+
def restore_toys_libs
|
294
|
+
$LOAD_PATH.delete(::Toys::CORE_LIB_PATH)
|
295
|
+
$LOAD_PATH.unshift(::Toys::CORE_LIB_PATH)
|
296
|
+
if ::Toys.const_defined?(:LIB_PATH)
|
297
|
+
$LOAD_PATH.delete(::Toys::LIB_PATH)
|
298
|
+
$LOAD_PATH.unshift(::Toys::LIB_PATH)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def permission_to_bundle?
|
303
|
+
case @on_missing
|
304
|
+
when :install
|
305
|
+
true
|
306
|
+
when :error
|
307
|
+
false
|
308
|
+
else
|
309
|
+
terminal.confirm("Your bundle requires additional gems. Install? ",
|
310
|
+
default: @default_confirm)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def install_bundle(gemfile_path)
|
315
|
+
gemfile_dir = ::File.dirname(gemfile_path)
|
316
|
+
unless permission_to_bundle?
|
317
|
+
raise BundleNotInstalledError,
|
318
|
+
"Your bundle is not installed. Consider running" \
|
319
|
+
" `cd #{gemfile_dir} && bundle install`"
|
320
|
+
end
|
321
|
+
require "bundler/cli"
|
322
|
+
::Bundler::CLI.start(["install"])
|
323
|
+
end
|
170
324
|
end
|
171
325
|
end
|
172
326
|
end
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -1,26 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
module Toys
|
25
4
|
module Utils
|
26
5
|
##
|
data/lib/toys/utils/terminal.rb
CHANGED
@@ -1,32 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright 2019 Daniel Azuma
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
21
|
-
# IN THE SOFTWARE.
|
22
|
-
;
|
23
|
-
|
24
3
|
require "stringio"
|
25
4
|
require "monitor"
|
26
5
|
|
27
6
|
begin
|
28
7
|
require "io/console"
|
29
|
-
rescue ::LoadError
|
8
|
+
rescue ::LoadError
|
30
9
|
# TODO: alternate methods of getting terminal size
|
31
10
|
end
|
32
11
|
|
@@ -146,6 +125,8 @@ module Toys
|
|
146
125
|
styled ? true : false
|
147
126
|
end
|
148
127
|
@named_styles = BUILTIN_STYLE_NAMES.dup
|
128
|
+
@output_mutex = ::Monitor.new
|
129
|
+
@input_mutex = ::Monitor.new
|
149
130
|
end
|
150
131
|
|
151
132
|
##
|
@@ -164,7 +145,7 @@ module Toys
|
|
164
145
|
# Whether output is styled
|
165
146
|
# @return [Boolean]
|
166
147
|
#
|
167
|
-
|
148
|
+
attr_reader :styled
|
168
149
|
|
169
150
|
##
|
170
151
|
# Write a partial line without appending a newline.
|
@@ -175,11 +156,41 @@ module Toys
|
|
175
156
|
# @return [self]
|
176
157
|
#
|
177
158
|
def write(str = "", *styles)
|
178
|
-
|
179
|
-
|
159
|
+
@output_mutex.synchronize do
|
160
|
+
begin
|
161
|
+
output&.write(apply_styles(str, *styles))
|
162
|
+
output&.flush
|
163
|
+
rescue ::IOError
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
180
167
|
self
|
181
168
|
end
|
182
169
|
|
170
|
+
##
|
171
|
+
# Read a line, blocking until one is available.
|
172
|
+
#
|
173
|
+
# @return [String] the entire string including the temrinating newline
|
174
|
+
# @return [nil] if the input is closed or at eof, or there is no input
|
175
|
+
#
|
176
|
+
def readline
|
177
|
+
@input_mutex.synchronize do
|
178
|
+
begin
|
179
|
+
input&.gets
|
180
|
+
rescue ::IOError
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# This method is defined so that `::Logger` will recognize a terminal as
|
188
|
+
# a log device target, but it does not actually close anything.
|
189
|
+
#
|
190
|
+
def close
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
183
194
|
##
|
184
195
|
# Write a line, appending a newline if one is not already present.
|
185
196
|
#
|
@@ -233,7 +244,7 @@ module Toys
|
|
233
244
|
prompt = "#{ptext} #{trailing_text}#{pspaces}"
|
234
245
|
end
|
235
246
|
write(prompt, *styles)
|
236
|
-
resp =
|
247
|
+
resp = readline.to_s.chomp
|
237
248
|
resp.empty? ? default.to_s : resp
|
238
249
|
end
|
239
250
|
|
@@ -296,13 +307,13 @@ module Toys
|
|
296
307
|
return nil unless block_given?
|
297
308
|
frame_length ||= DEFAULT_SPINNER_FRAME_LENGTH
|
298
309
|
frames ||= DEFAULT_SPINNER_FRAMES
|
299
|
-
|
310
|
+
write(leading_text) unless leading_text.empty?
|
300
311
|
spin = SpinDriver.new(self, frames, Array(style), frame_length)
|
301
312
|
begin
|
302
313
|
yield
|
303
314
|
ensure
|
304
315
|
spin.stop
|
305
|
-
|
316
|
+
write(final_text) unless final_text.empty?
|
306
317
|
end
|
307
318
|
end
|
308
319
|
|
@@ -312,8 +323,8 @@ module Toys
|
|
312
323
|
# @return [Array(Integer,Integer)]
|
313
324
|
#
|
314
325
|
def size
|
315
|
-
if
|
316
|
-
|
326
|
+
if output.respond_to?(:tty?) && output.tty? && output.respond_to?(:winsize)
|
327
|
+
output.winsize.reverse
|
317
328
|
else
|
318
329
|
[80, 25]
|
319
330
|
end
|
@@ -422,10 +433,8 @@ module Toys
|
|
422
433
|
|
423
434
|
## @private
|
424
435
|
class SpinDriver
|
425
|
-
include ::MonitorMixin
|
426
|
-
|
427
436
|
def initialize(terminal, frames, style, frame_length)
|
428
|
-
|
437
|
+
@mutex = ::Monitor.new
|
429
438
|
@terminal = terminal
|
430
439
|
@frames = frames.map do |f|
|
431
440
|
[@terminal.apply_styles(f, *style), Terminal.remove_style_escapes(f).size]
|
@@ -433,12 +442,12 @@ module Toys
|
|
433
442
|
@frame_length = frame_length
|
434
443
|
@cur_frame = 0
|
435
444
|
@stopping = false
|
436
|
-
@cond = new_cond
|
445
|
+
@cond = @mutex.new_cond
|
437
446
|
@thread = @terminal.output.tty? ? start_thread : nil
|
438
447
|
end
|
439
448
|
|
440
449
|
def stop
|
441
|
-
synchronize do
|
450
|
+
@mutex.synchronize do
|
442
451
|
@stopping = true
|
443
452
|
@cond.broadcast
|
444
453
|
end
|
@@ -450,12 +459,12 @@ module Toys
|
|
450
459
|
|
451
460
|
def start_thread
|
452
461
|
::Thread.new do
|
453
|
-
synchronize do
|
462
|
+
@mutex.synchronize do
|
454
463
|
until @stopping
|
455
|
-
@terminal.
|
464
|
+
@terminal.write(@frames[@cur_frame][0])
|
456
465
|
@cond.wait(@frame_length)
|
457
466
|
size = @frames[@cur_frame][1]
|
458
|
-
@terminal.
|
467
|
+
@terminal.write("\b" * size + " " * size + "\b" * size)
|
459
468
|
@cur_frame += 1
|
460
469
|
@cur_frame = 0 if @cur_frame >= @frames.size
|
461
470
|
end
|