toys-core 0.11.3 → 0.12.1
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 +47 -2
- data/README.md +1 -1
- data/lib/toys-core.rb +4 -1
- data/lib/toys/acceptor.rb +3 -3
- data/lib/toys/arg_parser.rb +6 -7
- data/lib/toys/cli.rb +44 -14
- data/lib/toys/compat.rb +19 -22
- data/lib/toys/completion.rb +3 -1
- data/lib/toys/context.rb +2 -2
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/base.rb +85 -0
- data/lib/toys/dsl/flag.rb +3 -3
- data/lib/toys/dsl/flag_group.rb +7 -7
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +3 -3
- data/lib/toys/dsl/tool.rb +174 -216
- data/lib/toys/errors.rb +1 -0
- data/lib/toys/flag.rb +15 -18
- data/lib/toys/flag_group.rb +5 -4
- data/lib/toys/input_file.rb +4 -4
- data/lib/toys/loader.rb +189 -50
- data/lib/toys/middleware.rb +1 -1
- data/lib/toys/mixin.rb +2 -2
- data/lib/toys/positional_arg.rb +3 -3
- data/lib/toys/settings.rb +900 -0
- data/lib/toys/source_info.rb +121 -18
- data/lib/toys/standard_middleware/apply_config.rb +5 -4
- data/lib/toys/standard_middleware/set_default_descriptions.rb +18 -18
- data/lib/toys/standard_middleware/show_help.rb +17 -5
- data/lib/toys/standard_mixins/bundler.rb +7 -2
- data/lib/toys/standard_mixins/exec.rb +22 -15
- data/lib/toys/standard_mixins/git_cache.rb +48 -0
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +2 -2
- data/lib/toys/{tool.rb → tool_definition.rb} +100 -41
- data/lib/toys/utils/exec.rb +12 -10
- data/lib/toys/utils/gems.rb +48 -14
- data/lib/toys/utils/git_cache.rb +184 -0
- data/lib/toys/utils/help_text.rb +90 -34
- data/lib/toys/utils/terminal.rb +1 -1
- data/lib/toys/utils/xdg.rb +293 -0
- metadata +15 -8
data/lib/toys/utils/gems.rb
CHANGED
@@ -132,8 +132,8 @@ module Toys
|
|
132
132
|
end
|
133
133
|
@on_conflict = on_conflict || :error
|
134
134
|
@terminal = terminal
|
135
|
-
@input = input ||
|
136
|
-
@output = output ||
|
135
|
+
@input = input || $stdin
|
136
|
+
@output = output || $stdout
|
137
137
|
end
|
138
138
|
|
139
139
|
##
|
@@ -170,22 +170,28 @@ module Toys
|
|
170
170
|
# recognized as Gemfiles, when searching because gemfile_path is not
|
171
171
|
# given. Defaults to {DEFAULT_GEMFILE_NAMES}.
|
172
172
|
#
|
173
|
+
# @param retries [Integer] Number of times to retry bundler operations.
|
174
|
+
# Optional.
|
175
|
+
#
|
173
176
|
# @return [void]
|
174
177
|
#
|
175
178
|
def bundle(groups: nil,
|
176
179
|
gemfile_path: nil,
|
177
180
|
search_dirs: nil,
|
178
|
-
gemfile_names: nil
|
181
|
+
gemfile_names: nil,
|
182
|
+
retries: nil)
|
179
183
|
Array(search_dirs).each do |dir|
|
180
184
|
break if gemfile_path
|
181
185
|
gemfile_path = Gems.find_gemfile(dir, gemfile_names: gemfile_names)
|
182
186
|
end
|
183
187
|
raise GemfileNotFoundError, "Gemfile not found" unless gemfile_path
|
188
|
+
gemfile_path = ::File.absolute_path(gemfile_path)
|
184
189
|
Gems.synchronize do
|
185
190
|
if configure_gemfile(gemfile_path)
|
186
191
|
activate("bundler", "~> 2.1")
|
187
192
|
require "bundler"
|
188
|
-
|
193
|
+
lockfile_path = find_lockfile_path(gemfile_path)
|
194
|
+
setup_bundle(gemfile_path, lockfile_path, groups: groups, retries: retries)
|
189
195
|
end
|
190
196
|
end
|
191
197
|
end
|
@@ -287,21 +293,47 @@ module Toys
|
|
287
293
|
true
|
288
294
|
end
|
289
295
|
|
290
|
-
def
|
296
|
+
def find_lockfile_path(gemfile_path)
|
297
|
+
if ::File.basename(gemfile_path) == "gems.rb"
|
298
|
+
::File.join(::File.dirname(gemfile_path), "gems.locked")
|
299
|
+
else
|
300
|
+
"#{gemfile_path}.lock"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def setup_bundle(gemfile_path, lockfile_path, groups: nil, retries: nil)
|
305
|
+
groups = Array(groups)
|
306
|
+
old_lockfile_contents = save_old_lockfile(lockfile_path)
|
291
307
|
begin
|
292
|
-
modify_bundle_definition(gemfile_path)
|
308
|
+
modify_bundle_definition(gemfile_path, lockfile_path)
|
293
309
|
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
294
310
|
rescue ::Bundler::GemNotFound, ::Bundler::VersionConflict
|
295
311
|
restore_toys_libs
|
296
|
-
install_bundle(gemfile_path)
|
312
|
+
install_bundle(gemfile_path, retries: retries)
|
313
|
+
old_lockfile_contents = save_old_lockfile(lockfile_path)
|
297
314
|
::Bundler.reset!
|
298
|
-
modify_bundle_definition(gemfile_path)
|
315
|
+
modify_bundle_definition(gemfile_path, lockfile_path)
|
299
316
|
::Bundler.ui.silence { ::Bundler.setup(*groups) }
|
300
317
|
end
|
301
318
|
restore_toys_libs
|
319
|
+
ensure
|
320
|
+
restore_old_lockfile(lockfile_path, old_lockfile_contents)
|
321
|
+
end
|
322
|
+
|
323
|
+
def save_old_lockfile(lockfile_path)
|
324
|
+
return nil unless ::File.readable?(lockfile_path) && ::File.writable?(lockfile_path)
|
325
|
+
::File.read(lockfile_path)
|
326
|
+
end
|
327
|
+
|
328
|
+
def restore_old_lockfile(lockfile_path, contents)
|
329
|
+
return unless contents
|
330
|
+
::File.open(lockfile_path, "w") do |file|
|
331
|
+
file.write(contents)
|
332
|
+
end
|
302
333
|
end
|
303
334
|
|
304
|
-
def modify_bundle_definition(gemfile_path)
|
335
|
+
def modify_bundle_definition(gemfile_path, lockfile_path)
|
336
|
+
::Bundler.configure
|
305
337
|
builder = ::Bundler::Dsl.new
|
306
338
|
builder.eval_gemfile(gemfile_path)
|
307
339
|
toys_gems = ["toys-core"]
|
@@ -312,7 +344,7 @@ module Toys
|
|
312
344
|
add_gem_to_definition(builder, "toys")
|
313
345
|
toys_gems << "toys"
|
314
346
|
end
|
315
|
-
definition = builder.to_definition(
|
347
|
+
definition = builder.to_definition(lockfile_path, { gems: toys_gems })
|
316
348
|
::Bundler.instance_variable_set(:@definition, definition)
|
317
349
|
end
|
318
350
|
|
@@ -357,19 +389,21 @@ module Toys
|
|
357
389
|
end
|
358
390
|
end
|
359
391
|
|
360
|
-
def install_bundle(gemfile_path)
|
392
|
+
def install_bundle(gemfile_path, retries: nil)
|
361
393
|
gemfile_dir = ::File.dirname(gemfile_path)
|
362
394
|
unless permission_to_bundle?
|
363
395
|
raise BundleNotInstalledError,
|
364
396
|
"Your bundle is not installed. Consider running" \
|
365
397
|
" `cd #{gemfile_dir} && bundle install`"
|
366
398
|
end
|
399
|
+
retries = retries.to_i
|
400
|
+
args = retries.positive? ? ["--retry=#{retries}"] : []
|
367
401
|
require "bundler/cli"
|
368
402
|
begin
|
369
|
-
::Bundler::CLI.start(["install"])
|
370
|
-
rescue ::Bundler::GemNotFound, ::Bundler::InstallError
|
403
|
+
::Bundler::CLI.start(["install"] + args)
|
404
|
+
rescue ::Bundler::GemNotFound, ::Bundler::InstallError, ::Bundler::VersionConflict
|
371
405
|
terminal.puts("Failed to install. Trying update...")
|
372
|
-
::Bundler::CLI.start(["update"])
|
406
|
+
::Bundler::CLI.start(["update"] + args)
|
373
407
|
end
|
374
408
|
end
|
375
409
|
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
require "fileutils"
|
5
|
+
require "toys/utils/exec"
|
6
|
+
require "toys/utils/xdg"
|
7
|
+
|
8
|
+
module Toys
|
9
|
+
module Utils
|
10
|
+
##
|
11
|
+
# This object provides cached access to remote git data. Given a remote
|
12
|
+
# repository, a path, and a commit, it makes the files availble in the
|
13
|
+
# local filesystem. Access is cached, so repeated requests do not hit the
|
14
|
+
# remote repository again.
|
15
|
+
#
|
16
|
+
# This class is used by the Loader to load tools from git. Tools can also
|
17
|
+
# use the `:git_cache` mixin for direct access to this class.
|
18
|
+
#
|
19
|
+
class GitCache
|
20
|
+
##
|
21
|
+
# GitCache encountered a failure
|
22
|
+
#
|
23
|
+
class Error < ::StandardError
|
24
|
+
##
|
25
|
+
# Create a GitCache::Error.
|
26
|
+
#
|
27
|
+
# @param message [String] The error message
|
28
|
+
# @param result [Toys::Utils::Exec::Result] The result of a git
|
29
|
+
# command execution, or `nil` if this error was not due to a git
|
30
|
+
# command error.
|
31
|
+
#
|
32
|
+
def initialize(message, result)
|
33
|
+
super(message)
|
34
|
+
@exec_result = result
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# @return [Toys::Utils::Exec::Result] The result of a git command
|
39
|
+
# execution, or `nil` if this error was not due to a git command
|
40
|
+
# error.
|
41
|
+
#
|
42
|
+
attr_reader :exec_result
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Access a git cache.
|
47
|
+
#
|
48
|
+
# @param cache_dir [String] The path to the cache directory. Defaults to
|
49
|
+
# a specific directory in the user's XDG cache.
|
50
|
+
#
|
51
|
+
def initialize(cache_dir: nil)
|
52
|
+
@cache_dir = ::File.expand_path(cache_dir || default_cache_dir)
|
53
|
+
@exec = Utils::Exec.new(out: :capture, err: :capture)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Find the given git-based files from the git cache, loading from the
|
58
|
+
# remote repo if necessary.
|
59
|
+
#
|
60
|
+
# @param remote [String] The URL of the git repo. Required.
|
61
|
+
# @param path [String] The path to the file or directory within the repo.
|
62
|
+
# Optional. Defaults to the entire repo.
|
63
|
+
# @param commit [String] The commit reference, which may be a SHA or any
|
64
|
+
# git ref such as a branch or tag. Optional. Defaults to `HEAD`.
|
65
|
+
# @param update [Boolean] Force update of non-SHA commit references, even
|
66
|
+
# if it has previously been loaded.
|
67
|
+
#
|
68
|
+
# @return [String] The full path to the cached files.
|
69
|
+
#
|
70
|
+
def find(remote, path: nil, commit: nil, update: false)
|
71
|
+
path ||= ""
|
72
|
+
commit ||= "HEAD"
|
73
|
+
dir = ensure_dir(remote)
|
74
|
+
lock_repo(dir) do
|
75
|
+
ensure_repo(dir, remote)
|
76
|
+
sha = ensure_commit(dir, commit, update)
|
77
|
+
ensure_source(dir, sha, path.to_s)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# The cache directory.
|
83
|
+
#
|
84
|
+
# @return [String]
|
85
|
+
#
|
86
|
+
attr_reader :cache_dir
|
87
|
+
|
88
|
+
# @private Used for testing
|
89
|
+
def repo_dir_for(remote)
|
90
|
+
::File.join(@cache_dir, remote_dir_name(remote), "repo")
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def remote_dir_name(remote)
|
96
|
+
::Digest::MD5.hexdigest(remote)
|
97
|
+
end
|
98
|
+
|
99
|
+
def source_name(sha, path)
|
100
|
+
digest = ::Digest::MD5.hexdigest("#{sha}#{path}")
|
101
|
+
"#{digest}#{::File.extname(path)}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def repo_dir_name
|
105
|
+
"repo"
|
106
|
+
end
|
107
|
+
|
108
|
+
def default_cache_dir
|
109
|
+
::File.join(XDG.new.cache_home, "toys", "git")
|
110
|
+
end
|
111
|
+
|
112
|
+
def git(dir, cmd, error_message: nil)
|
113
|
+
result = @exec.exec(["git"] + cmd, chdir: dir)
|
114
|
+
if result.failed?
|
115
|
+
raise GitCache::Error.new("Could not run git command line", result)
|
116
|
+
end
|
117
|
+
if block_given?
|
118
|
+
yield result
|
119
|
+
elsif result.error? && error_message
|
120
|
+
raise GitCache::Error.new(error_message, result)
|
121
|
+
else
|
122
|
+
result
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def ensure_dir(remote)
|
127
|
+
dir = ::File.join(@cache_dir, remote_dir_name(remote))
|
128
|
+
::FileUtils.mkdir_p(dir)
|
129
|
+
dir
|
130
|
+
end
|
131
|
+
|
132
|
+
def lock_repo(dir)
|
133
|
+
lock_path = ::File.join(dir, "repo.lock")
|
134
|
+
::File.open(lock_path, ::File::RDWR | ::File::CREAT) do |file|
|
135
|
+
file.flock(::File::LOCK_EX)
|
136
|
+
yield
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def ensure_repo(dir, remote)
|
141
|
+
repo_dir = ::File.join(dir, repo_dir_name)
|
142
|
+
::FileUtils.mkdir_p(repo_dir)
|
143
|
+
result = git(repo_dir, ["remote", "get-url", "origin"])
|
144
|
+
unless result.success? && result.captured_out.strip == remote
|
145
|
+
::FileUtils.rm_rf(repo_dir)
|
146
|
+
::FileUtils.mkdir_p(repo_dir)
|
147
|
+
git(repo_dir, ["init"],
|
148
|
+
error_message: "Unable to initialize git repository")
|
149
|
+
git(repo_dir, ["remote", "add", "origin", remote],
|
150
|
+
error_message: "Unable to add git remote")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def ensure_commit(dir, commit, update = false)
|
155
|
+
local_commit = "toys-git-cache/#{commit}"
|
156
|
+
repo_dir = ::File.join(dir, repo_dir_name)
|
157
|
+
is_sha = commit =~ /^[0-9a-f]{40}$/
|
158
|
+
if update && !is_sha || !commit_exists?(repo_dir, local_commit)
|
159
|
+
git(repo_dir, ["fetch", "--depth=1", "origin", "#{commit}:#{local_commit}"],
|
160
|
+
error_message: "Unable to to fetch commit: #{commit}")
|
161
|
+
end
|
162
|
+
result = git(repo_dir, ["rev-parse", local_commit],
|
163
|
+
error_message: "Unable to retrieve commit: #{local_commit}")
|
164
|
+
result.captured_out.strip
|
165
|
+
end
|
166
|
+
|
167
|
+
def commit_exists?(repo_dir, commit)
|
168
|
+
result = git(repo_dir, ["cat-file", "-t", commit])
|
169
|
+
result.success? && result.captured_out.strip == "commit"
|
170
|
+
end
|
171
|
+
|
172
|
+
def ensure_source(dir, sha, path)
|
173
|
+
source_path = ::File.join(dir, source_name(sha, path))
|
174
|
+
unless ::File.exist?(source_path)
|
175
|
+
repo_dir = ::File.join(dir, repo_dir_name)
|
176
|
+
git(repo_dir, ["checkout", sha])
|
177
|
+
from_path = ::File.join(repo_dir, path)
|
178
|
+
::FileUtils.cp_r(from_path, source_path)
|
179
|
+
end
|
180
|
+
source_path
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -44,11 +44,12 @@ module Toys
|
|
44
44
|
##
|
45
45
|
# Create a usage helper.
|
46
46
|
#
|
47
|
-
# @param tool [Toys::
|
47
|
+
# @param tool [Toys::ToolDefinition] The tool to document.
|
48
48
|
# @param loader [Toys::Loader] A loader that can provide subcommands.
|
49
49
|
# @param executable_name [String] The name of the executable.
|
50
50
|
# e.g. `"toys"`.
|
51
|
-
# @param delegates [Array<Toys::
|
51
|
+
# @param delegates [Array<Toys::ToolDefinition>] The delegation path to
|
52
|
+
# the tool.
|
52
53
|
#
|
53
54
|
# @return [Toys::Utils::HelpText]
|
54
55
|
#
|
@@ -60,8 +61,8 @@ module Toys
|
|
60
61
|
end
|
61
62
|
|
62
63
|
##
|
63
|
-
# The
|
64
|
-
# @return [Toys::
|
64
|
+
# The ToolDefinition being documented.
|
65
|
+
# @return [Toys::ToolDefinition]
|
65
66
|
#
|
66
67
|
attr_reader :tool
|
67
68
|
|
@@ -72,6 +73,8 @@ module Toys
|
|
72
73
|
# display all subtools recursively. Defaults to false.
|
73
74
|
# @param include_hidden [Boolean] Include hidden subtools (i.e. whose
|
74
75
|
# names begin with underscore.) Default is false.
|
76
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
77
|
+
# Defaults to false.
|
75
78
|
# @param left_column_width [Integer] Width of the first column. Default
|
76
79
|
# is {DEFAULT_LEFT_COLUMN_WIDTH}.
|
77
80
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
@@ -80,13 +83,14 @@ module Toys
|
|
80
83
|
#
|
81
84
|
# @return [String] A usage string.
|
82
85
|
#
|
83
|
-
def usage_string(recursive: false, include_hidden: false,
|
86
|
+
def usage_string(recursive: false, include_hidden: false, separate_sources: false,
|
84
87
|
left_column_width: nil, indent: nil, wrap_width: nil)
|
85
88
|
left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
|
86
89
|
indent ||= DEFAULT_INDENT
|
87
|
-
subtools =
|
90
|
+
subtools = collect_subtool_info(recursive, nil, include_hidden, separate_sources)
|
88
91
|
assembler = UsageStringAssembler.new(
|
89
|
-
@tool, @executable_name, subtools,
|
92
|
+
@tool, @executable_name, subtools, separate_sources,
|
93
|
+
indent, left_column_width, wrap_width
|
90
94
|
)
|
91
95
|
assembler.result
|
92
96
|
end
|
@@ -102,6 +106,8 @@ module Toys
|
|
102
106
|
# names begin with underscore.) Default is false.
|
103
107
|
# @param show_source_path [Boolean] If true, shows the source path
|
104
108
|
# section. Defaults to false.
|
109
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
110
|
+
# Defaults to false.
|
105
111
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
106
112
|
# @param indent2 [Integer] Second indent width. Default is
|
107
113
|
# {DEFAULT_INDENT}.
|
@@ -112,13 +118,14 @@ module Toys
|
|
112
118
|
# @return [String] A usage string.
|
113
119
|
#
|
114
120
|
def help_string(recursive: false, search: nil, include_hidden: false,
|
115
|
-
show_source_path: false,
|
121
|
+
show_source_path: false, separate_sources: false,
|
116
122
|
indent: nil, indent2: nil, wrap_width: nil, styled: true)
|
117
123
|
indent ||= DEFAULT_INDENT
|
118
124
|
indent2 ||= DEFAULT_INDENT
|
119
|
-
subtools =
|
125
|
+
subtools = collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
120
126
|
assembler = HelpStringAssembler.new(
|
121
|
-
@tool, @executable_name, @delegates, subtools, search,
|
127
|
+
@tool, @executable_name, @delegates, subtools, search,
|
128
|
+
show_source_path, separate_sources,
|
122
129
|
indent, indent2, wrap_width, styled
|
123
130
|
)
|
124
131
|
assembler.result
|
@@ -133,6 +140,8 @@ module Toys
|
|
133
140
|
# listing subtools. Defaults to `nil` which finds all subtools.
|
134
141
|
# @param include_hidden [Boolean] Include hidden subtools (i.e. whose
|
135
142
|
# names begin with underscore.) Default is false.
|
143
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
144
|
+
# Defaults to false.
|
136
145
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
137
146
|
# @param wrap_width [Integer,nil] Wrap width of the column, or `nil` to
|
138
147
|
# disable wrap. Default is `nil`.
|
@@ -141,17 +150,23 @@ module Toys
|
|
141
150
|
# @return [String] A usage string.
|
142
151
|
#
|
143
152
|
def list_string(recursive: false, search: nil, include_hidden: false,
|
144
|
-
indent: nil, wrap_width: nil, styled: true)
|
153
|
+
separate_sources: false, indent: nil, wrap_width: nil, styled: true)
|
145
154
|
indent ||= DEFAULT_INDENT
|
146
|
-
subtools =
|
147
|
-
assembler = ListStringAssembler.new(@tool, subtools, recursive, search,
|
155
|
+
subtools = collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
156
|
+
assembler = ListStringAssembler.new(@tool, subtools, recursive, search, separate_sources,
|
148
157
|
indent, wrap_width, styled)
|
149
158
|
assembler.result
|
150
159
|
end
|
151
160
|
|
152
161
|
private
|
153
162
|
|
154
|
-
def
|
163
|
+
def collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
164
|
+
subtools_by_name = list_subtools(recursive, include_hidden)
|
165
|
+
filter_subtools(subtools_by_name, search)
|
166
|
+
arrange_subtools(subtools_by_name, separate_sources)
|
167
|
+
end
|
168
|
+
|
169
|
+
def list_subtools(recursive, include_hidden)
|
155
170
|
subtools_by_name = {}
|
156
171
|
([@tool] + @delegates).each do |tool|
|
157
172
|
name_len = tool.full_name.length
|
@@ -162,20 +177,37 @@ module Toys
|
|
162
177
|
subtools_by_name[local_name] = subtool
|
163
178
|
end
|
164
179
|
end
|
180
|
+
subtools_by_name
|
181
|
+
end
|
182
|
+
|
183
|
+
def filter_subtools(subtools_by_name, search)
|
184
|
+
if !search.nil? && !search.empty?
|
185
|
+
regex = ::Regexp.new(search, ::Regexp::IGNORECASE)
|
186
|
+
subtools_by_name.delete_if do |local_name, tool|
|
187
|
+
!regex.match?(local_name) && !regex.match?(tool.desc.to_s)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def arrange_subtools(subtools_by_name, separate_sources)
|
165
193
|
subtool_list = subtools_by_name.sort_by { |(local_name, _tool)| local_name }
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
194
|
+
result = {}
|
195
|
+
subtool_list.each do |(local_name, subtool)|
|
196
|
+
key = separate_sources ? subtool.source_root : nil
|
197
|
+
(result[key] ||= []) << [local_name, subtool]
|
170
198
|
end
|
199
|
+
result.sort_by { |source, _subtools| -(source&.priority || -999_999) }
|
200
|
+
.map { |source, subtools| [source&.source_name || "unknown source", subtools] }
|
171
201
|
end
|
172
202
|
|
173
203
|
## @private
|
174
204
|
class UsageStringAssembler
|
175
|
-
def initialize(tool, executable_name, subtools,
|
205
|
+
def initialize(tool, executable_name, subtools, separate_sources,
|
206
|
+
indent, left_column_width, wrap_width)
|
176
207
|
@tool = tool
|
177
208
|
@executable_name = executable_name
|
178
209
|
@subtools = subtools
|
210
|
+
@separate_sources = separate_sources
|
179
211
|
@indent = indent
|
180
212
|
@left_column_width = left_column_width
|
181
213
|
@wrap_width = wrap_width
|
@@ -193,7 +225,8 @@ module Toys
|
|
193
225
|
add_flag_group_sections
|
194
226
|
add_positional_arguments_section if @tool.runnable?
|
195
227
|
add_subtool_list_section
|
196
|
-
|
228
|
+
joined_lines = @lines.join("\n")
|
229
|
+
@result = "#{joined_lines}\n"
|
197
230
|
end
|
198
231
|
|
199
232
|
def add_synopsis_section
|
@@ -226,7 +259,7 @@ module Toys
|
|
226
259
|
@lines << ""
|
227
260
|
desc_str = group.desc.to_s
|
228
261
|
desc_str = "Flags" if desc_str.empty?
|
229
|
-
@lines << desc_str
|
262
|
+
@lines << "#{desc_str}:"
|
230
263
|
group.flags.each do |flag|
|
231
264
|
add_flag(flag)
|
232
265
|
end
|
@@ -254,11 +287,12 @@ module Toys
|
|
254
287
|
end
|
255
288
|
|
256
289
|
def add_subtool_list_section
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
290
|
+
@subtools.each do |source_name, subtool_list|
|
291
|
+
@lines << ""
|
292
|
+
@lines << (@separate_sources ? "Tools from #{source_name}:" : "Tools:")
|
293
|
+
subtool_list.each do |local_name, subtool|
|
294
|
+
add_right_column_desc(local_name, wrap_desc(subtool.desc))
|
295
|
+
end
|
262
296
|
end
|
263
297
|
end
|
264
298
|
|
@@ -299,7 +333,7 @@ module Toys
|
|
299
333
|
## @private
|
300
334
|
class HelpStringAssembler
|
301
335
|
def initialize(tool, executable_name, delegates, subtools, search_term,
|
302
|
-
show_source_path, indent, indent2, wrap_width, styled)
|
336
|
+
show_source_path, separate_sources, indent, indent2, wrap_width, styled)
|
303
337
|
require "toys/utils/terminal"
|
304
338
|
@tool = tool
|
305
339
|
@executable_name = executable_name
|
@@ -307,6 +341,7 @@ module Toys
|
|
307
341
|
@subtools = subtools
|
308
342
|
@search_term = search_term
|
309
343
|
@show_source_path = show_source_path
|
344
|
+
@separate_sources = separate_sources
|
310
345
|
@indent = indent
|
311
346
|
@indent2 = indent2
|
312
347
|
@wrap_width = wrap_width
|
@@ -532,8 +567,17 @@ module Toys
|
|
532
567
|
@lines << indent_str("Showing search results for \"#{@search_term}\"")
|
533
568
|
@lines << ""
|
534
569
|
end
|
535
|
-
|
536
|
-
|
570
|
+
first_section = true
|
571
|
+
@subtools.each do |source_name, subtool_list|
|
572
|
+
@lines << "" unless first_section
|
573
|
+
if @separate_sources
|
574
|
+
@lines << indent_str(underline("From #{source_name}"))
|
575
|
+
@lines << ""
|
576
|
+
end
|
577
|
+
subtool_list.each do |local_name, subtool|
|
578
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
579
|
+
end
|
580
|
+
first_section = false
|
537
581
|
end
|
538
582
|
end
|
539
583
|
|
@@ -596,12 +640,14 @@ module Toys
|
|
596
640
|
|
597
641
|
## @private
|
598
642
|
class ListStringAssembler
|
599
|
-
def initialize(tool, subtools, recursive, search_term,
|
643
|
+
def initialize(tool, subtools, recursive, search_term, separate_sources,
|
644
|
+
indent, wrap_width, styled)
|
600
645
|
require "toys/utils/terminal"
|
601
646
|
@tool = tool
|
602
647
|
@subtools = subtools
|
603
648
|
@recursive = recursive
|
604
649
|
@search_term = search_term
|
650
|
+
@separate_sources = separate_sources
|
605
651
|
@indent = indent
|
606
652
|
@wrap_width = wrap_width
|
607
653
|
assemble(styled)
|
@@ -626,16 +672,22 @@ module Toys
|
|
626
672
|
else
|
627
673
|
"#{top_line} under #{bold(@tool.display_name)}:"
|
628
674
|
end
|
629
|
-
@lines << ""
|
630
675
|
if @search_term
|
631
|
-
@lines << "Showing search results for \"#{@search_term}\""
|
632
676
|
@lines << ""
|
677
|
+
@lines << "Showing search results for \"#{@search_term}\""
|
633
678
|
end
|
634
679
|
end
|
635
680
|
|
636
681
|
def add_list
|
637
|
-
@subtools.each do |
|
638
|
-
|
682
|
+
@subtools.each do |source_name, subtool_list|
|
683
|
+
@lines << ""
|
684
|
+
if @separate_sources
|
685
|
+
@lines << underline("From: #{source_name}")
|
686
|
+
@lines << ""
|
687
|
+
end
|
688
|
+
subtool_list.each do |local_name, subtool|
|
689
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
690
|
+
end
|
639
691
|
end
|
640
692
|
end
|
641
693
|
|
@@ -662,6 +714,10 @@ module Toys
|
|
662
714
|
@lines.apply_styles(str, :bold)
|
663
715
|
end
|
664
716
|
|
717
|
+
def underline(str)
|
718
|
+
@lines.apply_styles(str, :underline)
|
719
|
+
end
|
720
|
+
|
665
721
|
def indent_str(str)
|
666
722
|
"#{' ' * @indent}#{str}"
|
667
723
|
end
|