toys-core 0.10.5 → 0.11.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/CHANGELOG.md +62 -36
- data/docs/guide.md +2 -0
- data/lib/toys/acceptor.rb +1 -1
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/flag.rb +2 -2
- data/lib/toys/dsl/flag_group.rb +2 -2
- data/lib/toys/dsl/tool.rb +27 -6
- data/lib/toys/loader.rb +71 -31
- data/lib/toys/source_info.rb +22 -23
- data/lib/toys/standard_mixins/bundler.rb +113 -56
- data/lib/toys/utils/exec.rb +126 -34
- data/lib/toys/utils/gems.rb +71 -20
- data/lib/toys/utils/help_text.rb +54 -59
- metadata +5 -5
data/lib/toys/utils/gems.rb
CHANGED
@@ -71,6 +71,12 @@ module Toys
|
|
71
71
|
class IncompatibleToysError < BundlerFailedError
|
72
72
|
end
|
73
73
|
|
74
|
+
##
|
75
|
+
# The gemfile names that are searched by default.
|
76
|
+
# @return [Array<String>]
|
77
|
+
#
|
78
|
+
DEFAULT_GEMFILE_NAMES = [".gems.rb", "gems.rb", "Gemfile"].freeze
|
79
|
+
|
74
80
|
##
|
75
81
|
# Activate the given gem. If it is not present, attempt to install it (or
|
76
82
|
# inform the user to update the bundle).
|
@@ -149,27 +155,55 @@ module Toys
|
|
149
155
|
end
|
150
156
|
|
151
157
|
##
|
152
|
-
#
|
158
|
+
# Search for an appropriate Gemfile, and set up the bundle.
|
159
|
+
#
|
160
|
+
# @param groups [Array<String>] The groups to include in setup.
|
161
|
+
#
|
162
|
+
# @param gemfile_path [String] The path to the Gemfile to use. If `nil`
|
163
|
+
# or not given, the `:search_dirs` will be searched for a Gemfile.
|
164
|
+
#
|
165
|
+
# @param search_dirs [String,Array<String>] Directories in which to
|
166
|
+
# search for a Gemfile, if gemfile_path is not given. You can provide
|
167
|
+
# a single directory or an array of directories.
|
168
|
+
#
|
169
|
+
# @param gemfile_names [String,Array<String>] File names that are
|
170
|
+
# recognized as Gemfiles, when searching because gemfile_path is not
|
171
|
+
# given. Defaults to {DEFAULT_GEMFILE_NAMES}.
|
153
172
|
#
|
154
|
-
# @param groups [Array<String>] The groups to include in setup
|
155
|
-
# @param search_dirs [Array<String>] Directories to search for a Gemfile
|
156
173
|
# @return [void]
|
157
174
|
#
|
158
175
|
def bundle(groups: nil,
|
159
|
-
|
176
|
+
gemfile_path: nil,
|
177
|
+
search_dirs: nil,
|
178
|
+
gemfile_names: nil)
|
179
|
+
Array(search_dirs).each do |dir|
|
180
|
+
break if gemfile_path
|
181
|
+
gemfile_path = Gems.find_gemfile(dir, gemfile_names: gemfile_names)
|
182
|
+
end
|
183
|
+
raise GemfileNotFoundError, "Gemfile not found" unless gemfile_path
|
160
184
|
Gems.synchronize do
|
161
|
-
gemfile_path = find_gemfile(Array(search_dirs))
|
162
185
|
if configure_gemfile(gemfile_path)
|
163
186
|
activate("bundler", "~> 2.1")
|
164
187
|
require "bundler"
|
165
|
-
|
188
|
+
lockfile_path = find_lockfile_path(gemfile_path)
|
189
|
+
setup_bundle(gemfile_path, lockfile_path, groups || [])
|
166
190
|
end
|
167
191
|
end
|
168
192
|
end
|
169
193
|
|
194
|
+
# @private
|
195
|
+
def self.find_gemfile(search_dir, gemfile_names: nil)
|
196
|
+
gemfile_names ||= DEFAULT_GEMFILE_NAMES
|
197
|
+
Array(gemfile_names).each do |file|
|
198
|
+
gemfile_path = ::File.join(search_dir, file)
|
199
|
+
return gemfile_path if ::File.readable?(gemfile_path)
|
200
|
+
end
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
|
170
204
|
@global_mutex = ::Monitor.new
|
171
205
|
|
172
|
-
|
206
|
+
# @private
|
173
207
|
def self.synchronize(&block)
|
174
208
|
@global_mutex.synchronize(&block)
|
175
209
|
end
|
@@ -237,14 +271,6 @@ module Toys
|
|
237
271
|
raise ActivationFailedError, err.message
|
238
272
|
end
|
239
273
|
|
240
|
-
def find_gemfile(search_dirs)
|
241
|
-
search_dirs.each do |dir|
|
242
|
-
gemfile_path = ::File.join(dir, "Gemfile")
|
243
|
-
return gemfile_path if ::File.readable?(gemfile_path)
|
244
|
-
end
|
245
|
-
raise GemfileNotFoundError, "Gemfile not found"
|
246
|
-
end
|
247
|
-
|
248
274
|
def configure_gemfile(gemfile_path)
|
249
275
|
old_path = ::ENV["BUNDLE_GEMFILE"]
|
250
276
|
if old_path
|
@@ -262,21 +288,46 @@ module Toys
|
|
262
288
|
true
|
263
289
|
end
|
264
290
|
|
265
|
-
def
|
291
|
+
def find_lockfile_path(gemfile_path)
|
292
|
+
if ::File.basename(gemfile_path) == "gems.rb"
|
293
|
+
::File.join(::File.dirname(gemfile_path), "gems.locked")
|
294
|
+
else
|
295
|
+
gemfile_path + ".lock"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def setup_bundle(gemfile_path, lockfile_path, groups)
|
300
|
+
old_lockfile_contents = save_old_lockfile(lockfile_path)
|
266
301
|
begin
|
267
|
-
modify_bundle_definition(gemfile_path)
|
302
|
+
modify_bundle_definition(gemfile_path, lockfile_path)
|
268
303
|
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
269
304
|
rescue ::Bundler::GemNotFound, ::Bundler::VersionConflict
|
270
305
|
restore_toys_libs
|
271
306
|
install_bundle(gemfile_path)
|
307
|
+
old_lockfile_contents = save_old_lockfile(lockfile_path)
|
272
308
|
::Bundler.reset!
|
273
|
-
modify_bundle_definition(gemfile_path)
|
309
|
+
modify_bundle_definition(gemfile_path, lockfile_path)
|
274
310
|
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
275
311
|
end
|
276
312
|
restore_toys_libs
|
313
|
+
ensure
|
314
|
+
restore_old_lockfile(lockfile_path, old_lockfile_contents)
|
315
|
+
end
|
316
|
+
|
317
|
+
def save_old_lockfile(lockfile_path)
|
318
|
+
return nil unless ::File.readable?(lockfile_path) && ::File.writable?(lockfile_path)
|
319
|
+
::File.read(lockfile_path)
|
320
|
+
end
|
321
|
+
|
322
|
+
def restore_old_lockfile(lockfile_path, contents)
|
323
|
+
if contents
|
324
|
+
::File.open(lockfile_path, "w") do |file|
|
325
|
+
file.write(contents)
|
326
|
+
end
|
327
|
+
end
|
277
328
|
end
|
278
329
|
|
279
|
-
def modify_bundle_definition(gemfile_path)
|
330
|
+
def modify_bundle_definition(gemfile_path, lockfile_path)
|
280
331
|
builder = ::Bundler::Dsl.new
|
281
332
|
builder.eval_gemfile(gemfile_path)
|
282
333
|
toys_gems = ["toys-core"]
|
@@ -287,7 +338,7 @@ module Toys
|
|
287
338
|
add_gem_to_definition(builder, "toys")
|
288
339
|
toys_gems << "toys"
|
289
340
|
end
|
290
|
-
definition = builder.to_definition(
|
341
|
+
definition = builder.to_definition(lockfile_path, { gems: toys_gems })
|
291
342
|
::Bundler.instance_variable_set(:@definition, definition)
|
292
343
|
end
|
293
344
|
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -32,14 +32,13 @@ module Toys
|
|
32
32
|
# @return [Toys::Utils::HelpText]
|
33
33
|
#
|
34
34
|
def self.from_context(context)
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
delegates = []
|
36
|
+
cur = context
|
37
|
+
while (cur = cur[Context::Key::DELEGATED_FROM])
|
38
|
+
delegates << cur[Context::Key::TOOL]
|
38
39
|
end
|
39
|
-
delegate_target = orig_context == context ? nil : orig_context[Context::Key::TOOL_NAME]
|
40
40
|
cli = context[Context::Key::CLI]
|
41
|
-
new(context[Context::Key::TOOL], cli.loader, cli.executable_name,
|
42
|
-
delegate_target: delegate_target)
|
41
|
+
new(context[Context::Key::TOOL], cli.loader, cli.executable_name, delegates: delegates)
|
43
42
|
end
|
44
43
|
|
45
44
|
##
|
@@ -49,16 +48,15 @@ module Toys
|
|
49
48
|
# @param loader [Toys::Loader] A loader that can provide subcommands.
|
50
49
|
# @param executable_name [String] The name of the executable.
|
51
50
|
# e.g. `"toys"`.
|
52
|
-
# @param
|
53
|
-
# tool will delegate to. Default is `nil` for no delegation.
|
51
|
+
# @param delegates [Array<Toys::Tool>] The delegation path to the tool.
|
54
52
|
#
|
55
53
|
# @return [Toys::Utils::HelpText]
|
56
54
|
#
|
57
|
-
def initialize(tool, loader, executable_name,
|
55
|
+
def initialize(tool, loader, executable_name, delegates: [])
|
58
56
|
@tool = tool
|
59
57
|
@loader = loader
|
60
58
|
@executable_name = executable_name
|
61
|
-
@
|
59
|
+
@delegates = delegates
|
62
60
|
end
|
63
61
|
|
64
62
|
##
|
@@ -88,8 +86,7 @@ module Toys
|
|
88
86
|
indent ||= DEFAULT_INDENT
|
89
87
|
subtools = find_subtools(recursive, nil, include_hidden)
|
90
88
|
assembler = UsageStringAssembler.new(
|
91
|
-
@tool, @executable_name,
|
92
|
-
indent, left_column_width, wrap_width
|
89
|
+
@tool, @executable_name, subtools, indent, left_column_width, wrap_width
|
93
90
|
)
|
94
91
|
assembler.result
|
95
92
|
end
|
@@ -121,7 +118,7 @@ module Toys
|
|
121
118
|
indent2 ||= DEFAULT_INDENT
|
122
119
|
subtools = find_subtools(recursive, search, include_hidden)
|
123
120
|
assembler = HelpStringAssembler.new(
|
124
|
-
@tool, @executable_name, @
|
121
|
+
@tool, @executable_name, @delegates, subtools, search, show_source_path,
|
125
122
|
indent, indent2, wrap_width, styled
|
126
123
|
)
|
127
124
|
assembler.result
|
@@ -155,22 +152,29 @@ module Toys
|
|
155
152
|
private
|
156
153
|
|
157
154
|
def find_subtools(recursive, search, include_hidden)
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
subtools_by_name = {}
|
156
|
+
([@tool] + @delegates).each do |tool|
|
157
|
+
name_len = tool.full_name.length
|
158
|
+
subtools = @loader.list_subtools(tool.full_name,
|
159
|
+
recursive: recursive, include_hidden: include_hidden)
|
160
|
+
subtools.each do |subtool|
|
161
|
+
local_name = subtool.full_name.slice(name_len..-1).join(" ")
|
162
|
+
subtools_by_name[local_name] = subtool
|
163
|
+
end
|
164
|
+
end
|
165
|
+
subtool_list = subtools_by_name.sort_by { |(local_name, _tool)| local_name }
|
166
|
+
return subtool_list if search.nil? || search.empty?
|
161
167
|
regex = ::Regexp.new(search, ::Regexp::IGNORECASE)
|
162
|
-
|
163
|
-
regex =~
|
168
|
+
subtool_list.find_all do |local_name, tool|
|
169
|
+
regex =~ local_name || regex =~ tool.desc.to_s
|
164
170
|
end
|
165
171
|
end
|
166
172
|
|
167
173
|
## @private
|
168
174
|
class UsageStringAssembler
|
169
|
-
def initialize(tool, executable_name,
|
170
|
-
indent, left_column_width, wrap_width)
|
175
|
+
def initialize(tool, executable_name, subtools, indent, left_column_width, wrap_width)
|
171
176
|
@tool = tool
|
172
177
|
@executable_name = executable_name
|
173
|
-
@delegate_target = delegate_target
|
174
178
|
@subtools = subtools
|
175
179
|
@indent = indent
|
176
180
|
@left_column_width = left_column_width
|
@@ -195,7 +199,7 @@ module Toys
|
|
195
199
|
def add_synopsis_section
|
196
200
|
synopses = []
|
197
201
|
synopses << namespace_synopsis unless @subtools.empty?
|
198
|
-
synopses <<
|
202
|
+
synopses << tool_synopsis
|
199
203
|
first = true
|
200
204
|
synopses.each do |synopsis|
|
201
205
|
@lines << (first ? "Usage: #{synopsis}" : " #{synopsis}")
|
@@ -212,11 +216,6 @@ module Toys
|
|
212
216
|
synopsis.join(" ")
|
213
217
|
end
|
214
218
|
|
215
|
-
def delegate_synopsis
|
216
|
-
target = @delegate_target.join(" ")
|
217
|
-
"#{@executable_name} #{@tool.display_name} [ARGUMENTS FOR \"#{target}\"...]"
|
218
|
-
end
|
219
|
-
|
220
219
|
def namespace_synopsis
|
221
220
|
"#{@executable_name} #{@tool.display_name} TOOL [ARGUMENTS...]"
|
222
221
|
end
|
@@ -256,12 +255,10 @@ module Toys
|
|
256
255
|
|
257
256
|
def add_subtool_list_section
|
258
257
|
return if @subtools.empty?
|
259
|
-
name_len = @tool.full_name.length
|
260
258
|
@lines << ""
|
261
259
|
@lines << "Tools:"
|
262
|
-
@subtools.each do |subtool|
|
263
|
-
|
264
|
-
add_right_column_desc(tool_name, wrap_desc(subtool.desc))
|
260
|
+
@subtools.each do |local_name, subtool|
|
261
|
+
add_right_column_desc(local_name, wrap_desc(subtool.desc))
|
265
262
|
end
|
266
263
|
end
|
267
264
|
|
@@ -301,12 +298,12 @@ module Toys
|
|
301
298
|
|
302
299
|
## @private
|
303
300
|
class HelpStringAssembler
|
304
|
-
def initialize(tool, executable_name,
|
301
|
+
def initialize(tool, executable_name, delegates, subtools, search_term,
|
305
302
|
show_source_path, indent, indent2, wrap_width, styled)
|
306
303
|
require "toys/utils/terminal"
|
307
304
|
@tool = tool
|
308
305
|
@executable_name = executable_name
|
309
|
-
@
|
306
|
+
@delegates = delegates
|
310
307
|
@subtools = subtools
|
311
308
|
@search_term = search_term
|
312
309
|
@show_source_path = show_source_path
|
@@ -356,7 +353,7 @@ module Toys
|
|
356
353
|
@lines << ""
|
357
354
|
@lines << bold("SYNOPSIS")
|
358
355
|
add_synopsis_clause(namespace_synopsis) unless @subtools.empty?
|
359
|
-
add_synopsis_clause(@
|
356
|
+
add_synopsis_clause(tool_synopsis(@tool))
|
360
357
|
end
|
361
358
|
|
362
359
|
def add_synopsis_clause(synopsis)
|
@@ -367,8 +364,8 @@ module Toys
|
|
367
364
|
end
|
368
365
|
end
|
369
366
|
|
370
|
-
def tool_synopsis
|
371
|
-
synopsis = [full_executable_name]
|
367
|
+
def tool_synopsis(tool_for_name)
|
368
|
+
synopsis = [full_executable_name(tool_for_name)]
|
372
369
|
@tool.flag_groups.each do |flag_group|
|
373
370
|
case flag_group
|
374
371
|
when FlagGroup::Required
|
@@ -441,19 +438,14 @@ module Toys
|
|
441
438
|
end
|
442
439
|
|
443
440
|
def namespace_synopsis
|
444
|
-
synopsis = [full_executable_name
|
441
|
+
synopsis = [full_executable_name(@tool),
|
442
|
+
underline("TOOL"),
|
443
|
+
"[#{underline('ARGUMENTS')}...]"]
|
445
444
|
wrap_indent_indent2(WrappableString.new(synopsis))
|
446
445
|
end
|
447
446
|
|
448
|
-
def
|
449
|
-
|
450
|
-
args_clause = underline("ARGUMENTS FOR \"#{target}\"")
|
451
|
-
synopsis = [full_executable_name, "[#{args_clause}...]"]
|
452
|
-
wrap_indent_indent2(WrappableString.new(synopsis))
|
453
|
-
end
|
454
|
-
|
455
|
-
def full_executable_name
|
456
|
-
bold(([@executable_name] + @tool.full_name).join(" "))
|
447
|
+
def full_executable_name(tool_for_name)
|
448
|
+
bold(([@executable_name] + tool_for_name.full_name).join(" "))
|
457
449
|
end
|
458
450
|
|
459
451
|
def add_source_section
|
@@ -461,15 +453,22 @@ module Toys
|
|
461
453
|
@lines << ""
|
462
454
|
@lines << bold("SOURCE")
|
463
455
|
@lines << indent_str("Defined in #{@tool.source_info.source_name}")
|
456
|
+
@delegates.each do |delegate|
|
457
|
+
@lines << indent_str("Delegated from \"#{delegate.display_name}\"" \
|
458
|
+
" defined in #{delegate.source_info.source_name}")
|
459
|
+
end
|
464
460
|
end
|
465
461
|
|
466
462
|
def add_description_section
|
467
|
-
desc = @tool.long_desc
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
463
|
+
desc = @tool.long_desc.dup
|
464
|
+
@delegates.each do |delegate|
|
465
|
+
desc << "" << "Delegated from \"#{delegate.display_name}\""
|
466
|
+
unless delegate.long_desc.empty?
|
467
|
+
desc << ""
|
468
|
+
desc += delegate.long_desc
|
469
|
+
end
|
472
470
|
end
|
471
|
+
desc = desc[1..-1] if desc.first == ""
|
473
472
|
desc = wrap_indent(desc)
|
474
473
|
return if desc.empty?
|
475
474
|
@lines << ""
|
@@ -533,10 +532,8 @@ module Toys
|
|
533
532
|
@lines << indent_str("Showing search results for \"#{@search_term}\"")
|
534
533
|
@lines << ""
|
535
534
|
end
|
536
|
-
|
537
|
-
|
538
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
539
|
-
add_prefix_with_desc(bold(tool_name), subtool.desc)
|
535
|
+
@subtools.each do |local_name, subtool|
|
536
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
540
537
|
end
|
541
538
|
end
|
542
539
|
|
@@ -637,10 +634,8 @@ module Toys
|
|
637
634
|
end
|
638
635
|
|
639
636
|
def add_list
|
640
|
-
|
641
|
-
|
642
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
643
|
-
add_prefix_with_desc(bold(tool_name), subtool.desc)
|
637
|
+
@subtools.each do |local_name, subtool|
|
638
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
644
639
|
end
|
645
640
|
end
|
646
641
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toys-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Toys-Core is the command line tool framework underlying Toys. It can
|
14
14
|
be used to create command line executables using the Toys DSL and classes.
|
@@ -69,10 +69,10 @@ homepage: https://github.com/dazuma/toys
|
|
69
69
|
licenses:
|
70
70
|
- MIT
|
71
71
|
metadata:
|
72
|
-
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
72
|
+
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.11.4/file.CHANGELOG.html
|
73
73
|
source_code_uri: https://github.com/dazuma/toys
|
74
74
|
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
75
|
-
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
75
|
+
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.11.4
|
76
76
|
post_install_message:
|
77
77
|
rdoc_options: []
|
78
78
|
require_paths:
|
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
requirements: []
|
91
|
-
rubygems_version: 3.1.
|
91
|
+
rubygems_version: 3.1.4
|
92
92
|
signing_key:
|
93
93
|
specification_version: 4
|
94
94
|
summary: Framework for creating command line executables
|