toys-core 0.10.5 → 0.11.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Set up the bundle.
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
- search_dirs: nil)
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
- setup_bundle(gemfile_path, groups || [])
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
- ## @private
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 setup_bundle(gemfile_path, groups)
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(gemfile_path + ".lock", { gems: toys_gems })
341
+ definition = builder.to_definition(lockfile_path, { gems: toys_gems })
291
342
  ::Bundler.instance_variable_set(:@definition, definition)
292
343
  end
293
344
 
@@ -32,14 +32,13 @@ module Toys
32
32
  # @return [Toys::Utils::HelpText]
33
33
  #
34
34
  def self.from_context(context)
35
- orig_context = context
36
- while (from = context[Context::Key::DELEGATED_FROM])
37
- context = from
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 delegate_target [Array<String>,nil] The full name of a tool this
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, delegate_target: nil)
55
+ def initialize(tool, loader, executable_name, delegates: [])
58
56
  @tool = tool
59
57
  @loader = loader
60
58
  @executable_name = executable_name
61
- @delegate_target = delegate_target
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, @delegate_target, subtools,
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, @delegate_target, subtools, search, show_source_path,
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
- subtools = @loader.list_subtools(@tool.full_name,
159
- recursive: recursive, include_hidden: include_hidden)
160
- return subtools if search.nil? || search.empty?
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
- subtools.find_all do |tool|
163
- regex =~ tool.display_name || regex =~ tool.desc.to_s
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, delegate_target, subtools,
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 << (@delegate_target ? delegate_synopsis : tool_synopsis)
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
- tool_name = subtool.full_name.slice(name_len..-1).join(" ")
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, delegate_target, subtools, search_term,
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
- @delegate_target = delegate_target
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(@delegate_target ? delegate_synopsis : tool_synopsis)
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, underline("TOOL"), "[#{underline('ARGUMENTS')}...]"]
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 delegate_synopsis
449
- target = @delegate_target.join(" ")
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
- if @delegate_target
469
- delegate_clause =
470
- "Passes all arguments to \"#{@delegate_target.join(' ')}\" if invoked directly."
471
- desc = desc.empty? ? [delegate_clause] : desc + ["", delegate_clause]
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
- name_len = @tool.full_name.length
537
- @subtools.each do |subtool|
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
- name_len = @tool.full_name.length
641
- @subtools.each do |subtool|
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.10.5
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-07-19 00:00:00.000000000 Z
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.10.5/file.CHANGELOG.html
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.10.5
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.2
91
+ rubygems_version: 3.1.4
92
92
  signing_key:
93
93
  specification_version: 4
94
94
  summary: Framework for creating command line executables