toys-core 0.10.5 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f0e62a1c71082677fd2c257f95adec476cd8c1f5d83a8f42854e48cfe52d1bf
4
- data.tar.gz: d5940e36d77ce386ce7be949b2e16af08454095300a5beb3ffafdf4f691abe26
3
+ metadata.gz: 1cf20d64fb78cfa559000a8007d3f0567db38d1c66ea220daaf8a33f1275fcad
4
+ data.tar.gz: da6b5f0f2e7d7bef30ded0a45b17014e0adc623583b7221f67b6b44fc9ca3805
5
5
  SHA512:
6
- metadata.gz: a59b68df5403e100330518056d0e8d9b8792f9e01e36792b445413300e40e2637a86847af9afe565084a55e3bf3b332f1eff15cacc42a56c95597c94a47bd27a
7
- data.tar.gz: a8d943e4ccb7a2b9f3278a0eabf4c33485fb09d2fce2440d3f3c3cdc605a30977a0d43da993018b6affb51933686f69e038b5bf1847df54e1cdfab981af77309
6
+ metadata.gz: e429973defa957eb444c1395c9d6c235052f7f365978d216e7fc4633ebbb7361242ce649845304cd96c47d5227e563d79021c41bd0d2529e9d9b4695edb09ae6
7
+ data.tar.gz: 4cc2e28b0309130bd8c524a6ab3c17018e9160db4251225ad88aa082cf1ab1226eabda6d0e1ba8691d7ee2c107e8c6b4ea661265422d045a3a27692ce4b5745a
@@ -1,10 +1,20 @@
1
1
  # Release History
2
2
 
3
+ ### 0.11.0 / 2020-08-21
4
+
5
+ * ADDED: The load path can be truncated using the `truncate_load_path!` directive.
6
+ * IMPROVED: Generated help for delegates now includes the information for the target tool, plus subtools of the delegate.
7
+ * IMPROVED: The `:bundler` mixin searches for `gems.rb` and `.gems.rb` in addition to `Gemfile`.
8
+ * IMPROVED: The `:budnler` mixin can load a specific Gemfile path.
9
+ * FIXED: The loader can now find data and lib directories at the root level of a Toys directory.
10
+ * FIXED: Exec::Result correctly reports processes that terminated due to signals.
11
+ * FIXED: Fixed a rare Exec capture failure that resulted from a race condition when closing streams.
12
+
3
13
  ### 0.10.5 / 2020-07-18
4
14
 
5
15
  * IMPROVED: The bundler mixin silences bundler output during bundle setup.
6
16
  * IMPROVED: The bundler mixin allows toys and toys-core to be in the Gemfile. It checks their version requirements against the running Toys version, and either adds the corret version to the bundle or raises IncompatibleToysError.
7
- * IMPROVED: The bundler mixin utomatically updates the bundle if install fails (typically because a transitive dependency has been explicitly updated.)
17
+ * IMPROVED: The bundler mixin automatically updates the bundle if install fails (typically because a transitive dependency has been explicitly updated.)
8
18
  * FIXED: Some cases of transitive dependency handling by the bundler mixin.
9
19
  * FIXED: Fixed a crash when computing suggestions, when running with a bundle on Ruby 2.6 or earlier.
10
20
 
@@ -1,4 +1,6 @@
1
+ <!--
1
2
  # @title Toys-Core User Guide
3
+ -->
2
4
 
3
5
  # Toys-Core User Guide
4
6
 
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.10.5"
12
+ VERSION = "0.11.0"
13
13
  end
14
14
 
15
15
  ## @private deprecated
@@ -1619,6 +1619,23 @@ module Toys
1619
1619
  self
1620
1620
  end
1621
1621
 
1622
+ ##
1623
+ # Remove lower-priority sources from the load path. This prevents lower-
1624
+ # priority sources (such as Toys files from parent or global directories)
1625
+ # from executing or defining tools.
1626
+ #
1627
+ # This works only if no such sources have already loaded yet.
1628
+ #
1629
+ # @raise [Toys::ToolDefinitionError] if any lower-priority tools have
1630
+ # already been loaded.
1631
+ #
1632
+ def truncate_load_path!
1633
+ unless @__loader.stop_loading_at_priority(@__priority)
1634
+ raise ToolDefinitionError,
1635
+ "Cannot truncate load path because tools have already been loaded"
1636
+ end
1637
+ end
1638
+
1622
1639
  ##
1623
1640
  # Determines whether the current Toys version satisfies the given
1624
1641
  # requirements.
@@ -8,6 +8,9 @@ module Toys
8
8
  # appropriate tool given a set of command line arguments.
9
9
  #
10
10
  class Loader
11
+ # @private
12
+ BASE_PRIORITY = -999_999
13
+
11
14
  ##
12
15
  # Create a Loader
13
16
  #
@@ -69,9 +72,11 @@ module Toys
69
72
  @worklist = []
70
73
  @tool_data = {}
71
74
  @max_priority = @min_priority = 0
75
+ @stop_priority = BASE_PRIORITY
76
+ @min_loaded_priority = 999_999
72
77
  @middleware_stack = Middleware.stack(middleware_stack)
73
78
  @delimiter_handler = DelimiterHandler.new(extra_delimiters)
74
- get_tool([], -999_999)
79
+ get_tool([], BASE_PRIORITY)
75
80
  end
76
81
 
77
82
  ##
@@ -89,7 +94,7 @@ module Toys
89
94
  raise "Cannot add a path after tool loading has started" if @loading_started
90
95
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
91
96
  paths.each do |path|
92
- source = SourceInfo.create_path_root(path)
97
+ source = SourceInfo.create_path_root(path, @data_dir_name, @lib_dir_name)
93
98
  @worklist << [source, [], priority]
94
99
  end
95
100
  end
@@ -114,7 +119,7 @@ module Toys
114
119
  @mutex.synchronize do
115
120
  raise "Cannot add a block after tool loading has started" if @loading_started
116
121
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
117
- source = SourceInfo.create_proc_root(block, name)
122
+ source = SourceInfo.create_proc_root(block, name, @data_dir_name, @lib_dir_name)
118
123
  @worklist << [source, [], priority]
119
124
  end
120
125
  self
@@ -178,15 +183,15 @@ module Toys
178
183
  load_for_prefix(words)
179
184
  found_tools = []
180
185
  len = words.length
181
- tool_data_snapshot.each do |n, td|
182
- next if n.empty?
186
+ all_cur_definitions.each do |tool|
187
+ name = tool.full_name
188
+ next if name.empty?
183
189
  if recursive
184
- next if n.length <= len || n.slice(0, len) != words
190
+ next if name.length <= len || name.slice(0, len) != words
185
191
  else
186
- next unless n.slice(0..-2) == words
192
+ next unless name.slice(0..-2) == words
187
193
  end
188
- tool = td.cur_definition
189
- found_tools << tool unless tool.nil?
194
+ found_tools << tool
190
195
  end
191
196
  sort_tools_by_name(found_tools)
192
197
  include_hidden ? found_tools : filter_hidden_subtools(found_tools)
@@ -202,8 +207,9 @@ module Toys
202
207
  def has_subtools?(words) # rubocop:disable Naming/PredicateName
203
208
  load_for_prefix(words)
204
209
  len = words.length
205
- tool_data_snapshot.each do |n, td|
206
- if !n.empty? && n.length > len && n.slice(0, len) == words && !td.empty?
210
+ all_cur_definitions.each do |tool|
211
+ name = tool.full_name
212
+ if !name.empty? && name.length > len && name.slice(0, len) == words
207
213
  return true
208
214
  end
209
215
  end
@@ -267,6 +273,20 @@ module Toys
267
273
  Tool.new(self, parent, words, priority, middleware_stack, @middleware_lookup)
268
274
  end
269
275
 
276
+ ##
277
+ # Stop search at the given priority. Returns true if successful.
278
+ # Called only from the DSL.
279
+ #
280
+ # @private
281
+ #
282
+ def stop_loading_at_priority(priority)
283
+ @mutex.synchronize do
284
+ return false if priority > @min_loaded_priority || priority < @stop_priority
285
+ @stop_priority = priority
286
+ true
287
+ end
288
+ end
289
+
270
290
  ##
271
291
  # Loads the subtree under the given prefix.
272
292
  #
@@ -278,6 +298,7 @@ module Toys
278
298
  cur_worklist = @worklist
279
299
  @worklist = []
280
300
  cur_worklist.each do |source, words, priority|
301
+ next if priority < @stop_priority
281
302
  remaining_words = calc_remaining_words(prefix, words)
282
303
  if source.source_proc
283
304
  load_proc(source, words, remaining_words, priority)
@@ -349,8 +370,15 @@ module Toys
349
370
 
350
371
  private
351
372
 
352
- def tool_data_snapshot
353
- @mutex.synchronize { @tool_data.dup }
373
+ def all_cur_definitions
374
+ result = []
375
+ @mutex.synchronize do
376
+ @tool_data.map do |_name, td|
377
+ tool = td.cur_definition
378
+ result << tool unless tool.nil?
379
+ end
380
+ end
381
+ result
354
382
  end
355
383
 
356
384
  def get_tool_data(words)
@@ -364,14 +392,16 @@ module Toys
364
392
  def finish_definitions_in_tree(words)
365
393
  load_for_prefix(words)
366
394
  len = words.length
367
- tool_data_snapshot.each do |n, td|
368
- next if n.length < len || n.slice(0, len) != words
369
- td.cur_definition&.finish_definition(self)
395
+ all_cur_definitions.each do |tool|
396
+ name = tool.full_name
397
+ next if name.length < len || name.slice(0, len) != words
398
+ tool.finish_definition(self)
370
399
  end
371
400
  end
372
401
 
373
402
  def load_proc(source, words, remaining_words, priority)
374
403
  if remaining_words
404
+ update_min_loaded_priority(priority)
375
405
  tool_class = get_tool(words, priority).tool_class
376
406
  DSL::Tool.prepare(tool_class, remaining_words, source) do
377
407
  ContextualError.capture("Error while loading Toys config!") do
@@ -393,6 +423,7 @@ module Toys
393
423
 
394
424
  def load_relevant_path(source, words, remaining_words, priority)
395
425
  if source.source_type == :file
426
+ update_min_loaded_priority(priority)
396
427
  tool_class = get_tool(words, priority).tool_class
397
428
  InputFile.evaluate(tool_class, remaining_words, source)
398
429
  else
@@ -406,7 +437,7 @@ module Toys
406
437
 
407
438
  def load_index_in(source, words, remaining_words, priority)
408
439
  return unless @index_file_name
409
- index_source = source.relative_child(@index_file_name, @data_dir_name, @lib_dir_name)
440
+ index_source = source.relative_child(@index_file_name)
410
441
  load_relevant_path(index_source, words, remaining_words, priority) if index_source
411
442
  end
412
443
 
@@ -414,7 +445,7 @@ module Toys
414
445
  return if child.start_with?(".") || child == @index_file_name ||
415
446
  child == @preload_file_name || child == @preload_dir_name ||
416
447
  child == @data_dir_name || child == @lib_dir_name
417
- child_source = source.relative_child(child, @data_dir_name, @lib_dir_name)
448
+ child_source = source.relative_child(child)
418
449
  return unless child_source
419
450
  child_word = ::File.basename(child, ".rb")
420
451
  next_words = words + [child_word]
@@ -422,6 +453,10 @@ module Toys
422
453
  load_validated_path(child_source, next_words, next_remaining, priority)
423
454
  end
424
455
 
456
+ def update_min_loaded_priority(priority)
457
+ @min_loaded_priority = priority if @min_loaded_priority > priority
458
+ end
459
+
425
460
  def do_preload(path)
426
461
  if @preload_file_name
427
462
  preload_file = ::File.join(path, @preload_file_name)
@@ -483,37 +518,42 @@ module Toys
483
518
  # @private
484
519
  #
485
520
  class ToolData
486
- ## @private
521
+ # @private
487
522
  def initialize(words)
488
523
  @words = words
489
524
  @definitions = {}
490
525
  @top_priority = @active_priority = nil
526
+ @mutex = ::Monitor.new
491
527
  end
492
528
 
493
- ## @private
529
+ # @private
494
530
  def cur_definition
495
- active_definition || top_definition
531
+ @mutex.synchronize { active_definition || top_definition }
496
532
  end
497
533
 
498
- ## @private
534
+ # @private
499
535
  def empty?
500
536
  @definitions.empty?
501
537
  end
502
538
 
503
- ## @private
539
+ # @private
504
540
  def get_tool(priority, loader)
505
- if @top_priority.nil? || @top_priority < priority
506
- @top_priority = priority
541
+ @mutex.synchronize do
542
+ if @top_priority.nil? || @top_priority < priority
543
+ @top_priority = priority
544
+ end
545
+ @definitions[priority] ||= loader.build_tool(@words, priority)
507
546
  end
508
- @definitions[priority] ||= loader.build_tool(@words, priority)
509
547
  end
510
548
 
511
- ## @private
549
+ # @private
512
550
  def activate_tool(priority, loader)
513
- return active_definition if @active_priority == priority
514
- return nil if @active_priority && @active_priority > priority
515
- @active_priority = priority
516
- get_tool(priority, loader)
551
+ @mutex.synchronize do
552
+ return active_definition if @active_priority == priority
553
+ return nil if @active_priority && @active_priority > priority
554
+ @active_priority = priority
555
+ get_tool(priority, loader)
556
+ end
517
557
  end
518
558
 
519
559
  private
@@ -10,7 +10,7 @@ module Toys
10
10
  # @private
11
11
  #
12
12
  def initialize(parent, context_directory, source_type, source_path, source_proc,
13
- source_name, data_dir, lib_dir)
13
+ source_name, data_dir_name, lib_dir_name)
14
14
  @parent = parent
15
15
  @context_directory = context_directory
16
16
  @source_type = source_type
@@ -18,8 +18,10 @@ module Toys
18
18
  @source_path = source_path
19
19
  @source_proc = source_proc
20
20
  @source_name = source_name
21
- @data_dir = data_dir
22
- @lib_dir = lib_dir
21
+ @data_dir_name = data_dir_name
22
+ @lib_dir_name = lib_dir_name
23
+ @data_dir = find_special_dir(data_dir_name)
24
+ @lib_dir = find_special_dir(lib_dir_name)
23
25
  end
24
26
 
25
27
  ##
@@ -118,15 +120,13 @@ module Toys
118
120
  # Create a child SourceInfo relative to the parent path.
119
121
  # @private
120
122
  #
121
- def relative_child(filename, data_dir_name, lib_dir_name)
122
- raise "no parent path for relative_child" unless source_path
123
+ def relative_child(filename)
124
+ raise "relative_child is valid only on a directory source" unless source_type == :directory
123
125
  child_path = ::File.join(source_path, filename)
124
126
  child_path, type = SourceInfo.check_path(child_path, true)
125
127
  return nil unless child_path
126
- data_dir = SourceInfo.find_special_dir(type, child_path, data_dir_name)
127
- lib_dir = SourceInfo.find_special_dir(type, child_path, lib_dir_name)
128
- SourceInfo.new(self, context_directory, type, child_path, source_proc, child_path,
129
- data_dir, lib_dir)
128
+ SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
129
+ @data_dir_name, @lib_dir_name)
130
130
  end
131
131
 
132
132
  ##
@@ -135,7 +135,8 @@ module Toys
135
135
  #
136
136
  def absolute_child(child_path)
137
137
  child_path, type = SourceInfo.check_path(child_path, false)
138
- SourceInfo.new(self, context_directory, type, child_path, source_proc, child_path, nil, nil)
138
+ SourceInfo.new(self, context_directory, type, child_path, nil, child_path,
139
+ @data_dir_name, @lib_dir_name)
139
140
  end
140
141
 
141
142
  ##
@@ -144,25 +145,26 @@ module Toys
144
145
  #
145
146
  def proc_child(child_proc, source_name = nil)
146
147
  source_name ||= self.source_name
147
- SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name, nil, nil)
148
+ SourceInfo.new(self, context_directory, :proc, source_path, child_proc, source_name,
149
+ @data_dir_name, @lib_dir_name)
148
150
  end
149
151
 
150
152
  ##
151
153
  # Create a root source info for a file path.
152
154
  # @private
153
155
  #
154
- def self.create_path_root(source_path)
156
+ def self.create_path_root(source_path, data_dir_name, lib_dir_name)
155
157
  source_path, type = check_path(source_path, false)
156
158
  context_directory = ::File.dirname(source_path)
157
- new(nil, context_directory, type, source_path, nil, source_path, nil, nil)
159
+ new(nil, context_directory, type, source_path, nil, source_path, data_dir_name, lib_dir_name)
158
160
  end
159
161
 
160
162
  ##
161
163
  # Create a root source info for a proc.
162
164
  # @private
163
165
  #
164
- def self.create_proc_root(source_proc, source_name)
165
- new(nil, nil, :proc, nil, source_proc, source_name, nil, nil)
166
+ def self.create_proc_root(source_proc, source_name, data_dir_name, lib_dir_name)
167
+ new(nil, nil, :proc, nil, source_proc, source_name, data_dir_name, lib_dir_name)
166
168
  end
167
169
 
168
170
  ##
@@ -189,14 +191,11 @@ module Toys
189
191
  end
190
192
  end
191
193
 
192
- ##
193
- # Determine the data directory path, if any.
194
- # @private
195
- #
196
- def self.find_special_dir(type, source_path, dir_name)
197
- return nil if source_path.nil? || dir_name.nil?
198
- source_path = ::File.dirname(source_path) if type == :file
199
- dir = ::File.join(source_path, dir_name)
194
+ private
195
+
196
+ def find_special_dir(dir_name)
197
+ return nil if @source_type != :directory || dir_name.nil?
198
+ dir = ::File.join(@source_path, dir_name)
200
199
  dir if ::File.directory?(dir) && ::File.readable?(dir)
201
200
  end
202
201
  end
@@ -11,32 +11,51 @@ module Toys
11
11
  # defining the tool. If `false` (the default), installs the bundle just
12
12
  # before the tool runs.
13
13
  #
14
- # * `:groups` (Array<String>) The groups to include in setup
14
+ # * `:groups` (Array\<String\>) The groups to include in setup.
15
15
  #
16
- # * `:search_dirs` (Array<String,Symbol>) Directories to search for a
17
- # Gemfile.
16
+ # * `:gemfile_path` (String) The path to the Gemfile to use. If `nil` or
17
+ # not given, the `:search_dirs` will be searched for a Gemfile.
18
+ #
19
+ # * `:search_dirs` (String,Symbol,Array\<String,Symbol\>) Directories to
20
+ # search for a Gemfile.
18
21
  #
19
22
  # You can pass full directory paths, and/or any of the following:
20
- # * `:context` - the current context directory
21
- # * `:current` - the current working directory
22
- # * `:toys` - the Toys directory containing the tool definition
23
+ # * `:context` - the current context directory.
24
+ # * `:current` - the current working directory.
25
+ # * `:toys` - the Toys directory containing the tool definition, and
26
+ # any of its parents within the Toys directory hierarchy.
23
27
  #
24
28
  # The default is to search `[:toys, :context, :current]` in that order.
29
+ # See {DEFAULT_SEARCH_DIRS}.
30
+ #
31
+ # For most directories, the bundler mixin will look for the files
32
+ # ".gems.rb", "gems.rb", and "Gemfile", in that order. In `:toys`
33
+ # directories, it will look only for ".gems.rb" and "Gemfile", in that
34
+ # order. These can be overridden by setting the `:gemfile_names` and/or
35
+ # `:toys_gemfile_names` arguments.
36
+ #
37
+ # * `:gemfile_names` (Array\<String\>) File names that are recognized as
38
+ # Gemfiles when searching in directories other than Toys directories.
39
+ # Defaults to {Toys::Utils::Gems::DEFAULT_GEMFILE_NAMES}.
40
+ #
41
+ # * `:toys_gemfile_names` (Array\<String\>) File names that are
42
+ # recognized as Gemfiles when wearching in Toys directories.
43
+ # Defaults to {DEFAULT_TOYS_GEMFILE_NAMES}.
25
44
  #
26
45
  # * `:on_missing` (Symbol) What to do if a needed gem is not installed.
27
46
  #
28
47
  # Supported values:
29
- # * `:confirm` - prompt the user on whether to install (default)
30
- # * `:error` - raise an exception
31
- # * `:install` - just install the gem
48
+ # * `:confirm` - prompt the user on whether to install (default).
49
+ # * `:error` - raise an exception.
50
+ # * `:install` - just install the gem.
32
51
  #
33
52
  # * `:on_conflict` (Symbol) What to do if bundler has already been run
34
53
  # with a different Gemfile.
35
54
  #
36
55
  # Supported values:
37
- # * `:error` - raise an exception (default)
38
- # * `:ignore` - just silently proceed without bundling again
39
- # * `:warn` - print a warning and proceed without bundling again
56
+ # * `:error` - raise an exception (default).
57
+ # * `:ignore` - just silently proceed without bundling again.
58
+ # * `:warn` - print a warning and proceed without bundling again.
40
59
  #
41
60
  # * `:terminal` (Toys::Utils::Terminal) Terminal to use (optional)
42
61
  # * `:input` (IO) Input IO (optional, defaults to STDIN)
@@ -45,68 +64,106 @@ module Toys
45
64
  module Bundler
46
65
  include Mixin
47
66
 
48
- on_initialize do |static: false, search_dirs: nil, **kwargs|
67
+ on_initialize do |static: false, **kwargs|
49
68
  unless static
50
- require "toys/utils/gems"
51
- search_dirs = ::Toys::StandardMixins::Bundler.resolve_search_dirs(
52
- search_dirs,
53
- self[::Toys::Context::Key::CONTEXT_DIRECTORY],
54
- self[::Toys::Context::Key::TOOL_SOURCE]
55
- )
56
- ::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
69
+ context_directory = self[::Toys::Context::Key::CONTEXT_DIRECTORY]
70
+ source_info = self[::Toys::Context::Key::TOOL_SOURCE]
71
+ ::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
57
72
  end
58
73
  end
59
74
 
60
- on_include do |static: false, search_dirs: nil, **kwargs|
75
+ on_include do |static: false, **kwargs|
61
76
  if static
62
- require "toys/utils/gems"
63
- search_dirs = ::Toys::StandardMixins::Bundler.resolve_search_dirs(
64
- search_dirs, context_directory, source_info
65
- )
66
- ::Toys::StandardMixins::Bundler.setup_bundle(search_dirs, **kwargs)
77
+ ::Toys::StandardMixins::Bundler.setup_bundle(context_directory, source_info, **kwargs)
67
78
  end
68
79
  end
69
80
 
70
- ## @private
71
- def self.resolve_search_dirs(search_dirs, context_dir, source_info)
72
- search_dirs ||= [:toys, :context, :current]
73
- Array(search_dirs).flat_map do |dir|
74
- case dir
75
- when :context
76
- context_dir
77
- when :current
78
- ::Dir.getwd
79
- when :toys
80
- toys_dir_stack(source_info)
81
- when ::String
82
- dir
83
- else
84
- raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
85
- end
86
- end
87
- end
81
+ ##
82
+ # Default search directories for Gemfiles.
83
+ # @return [Array<String,Symbol>]
84
+ #
85
+ DEFAULT_SEARCH_DIRS = [:toys, :context, :current].freeze
88
86
 
89
- ## @private
90
- def self.toys_dir_stack(source_info)
91
- dirs = []
92
- while source_info
93
- dirs << source_info.source_path if source_info.source_type == :directory
94
- source_info = source_info.parent
95
- end
96
- dirs
97
- end
87
+ ##
88
+ # The gemfile names that are searched by default in Toys directories.
89
+ # @return [Array<String>]
90
+ #
91
+ DEFAULT_TOYS_GEMFILE_NAMES = [".gems.rb", "Gemfile"].freeze
98
92
 
99
- ## @private
100
- def self.setup_bundle(search_dirs,
93
+ # @private
94
+ def self.setup_bundle(context_directory,
95
+ source_info,
96
+ gemfile_path: nil,
97
+ search_dirs: nil,
98
+ gemfile_names: nil,
99
+ toys_gemfile_names: nil,
101
100
  groups: nil,
102
101
  on_missing: nil,
103
102
  on_conflict: nil,
104
103
  terminal: nil,
105
104
  input: nil,
106
105
  output: nil)
106
+ require "toys/utils/gems"
107
+ gemfile_path ||= begin
108
+ gemfile_finder = GemfileFinder.new(context_directory, source_info,
109
+ gemfile_names, toys_gemfile_names)
110
+ gemfile_finder.search(search_dirs || DEFAULT_SEARCH_DIRS)
111
+ end
107
112
  gems = ::Toys::Utils::Gems.new(on_missing: on_missing, on_conflict: on_conflict,
108
113
  terminal: terminal, input: input, output: output)
109
- gems.bundle(groups: groups, search_dirs: search_dirs)
114
+ gems.bundle(groups: groups, gemfile_path: gemfile_path)
115
+ end
116
+
117
+ # @private
118
+ class GemfileFinder
119
+ # @private
120
+ def initialize(context_directory, source_info, gemfile_names, toys_gemfile_names)
121
+ @context_directory = context_directory
122
+ @source_info = source_info
123
+ @gemfile_names = gemfile_names
124
+ @toys_gemfile_names = toys_gemfile_names || DEFAULT_TOYS_GEMFILE_NAMES
125
+ end
126
+
127
+ # @private
128
+ def search(search_dir)
129
+ case search_dir
130
+ when ::Array
131
+ search_array(search_dir)
132
+ when ::String
133
+ ::Toys::Utils::Gems.find_gemfile(search_dir, gemfile_names: @gemfile_names)
134
+ when :context
135
+ search(@context_directory)
136
+ when :current
137
+ search(::Dir.getwd)
138
+ when :toys
139
+ search_toys
140
+ else
141
+ raise ::ArgumentError, "Unrecognized search_dir: #{dir.inspect}"
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def search_array(search_dirs)
148
+ search_dirs.each do |search_dir|
149
+ result = search(search_dir)
150
+ return result if result
151
+ end
152
+ nil
153
+ end
154
+
155
+ def search_toys
156
+ source_info = @source_info
157
+ while source_info
158
+ if source_info.source_type == :directory
159
+ result = ::Toys::Utils::Gems.find_gemfile(source_info.source_path,
160
+ gemfile_names: @toys_gemfile_names)
161
+ return result if result
162
+ end
163
+ source_info = source_info.parent
164
+ end
165
+ nil
166
+ end
110
167
  end
111
168
  end
112
169
  end
@@ -547,7 +547,7 @@ module Toys
547
547
  ##
548
548
  # The process ID.
549
549
  #
550
- # Exactly one of `exception` and `pid` will be non-nil.
550
+ # Exactly one of {#exception} and {#pid} will be non-nil.
551
551
  #
552
552
  # @return [Integer] if the process start was successful
553
553
  # @return [nil] if the process could not be started.
@@ -557,7 +557,7 @@ module Toys
557
557
  ##
558
558
  # The exception raised when the process failed to start.
559
559
  #
560
- # Exactly one of `exception` and `pid` will be non-nil.
560
+ # Exactly one of {#exception} and {#pid} will be non-nil.
561
561
  #
562
562
  # @return [Exception] if the process failed to start.
563
563
  # @return [nil] if the process start was successful.
@@ -722,15 +722,20 @@ module Toys
722
722
  ##
723
723
  # Wait for the subcommand to complete, and return a result object.
724
724
  #
725
+ # Closes the control streams if present. The stdin stream is always
726
+ # closed, even if the call times out. The stdout and stderr streams are
727
+ # closed only after the command terminates.
728
+ #
725
729
  # @param timeout [Numeric,nil] The timeout in seconds, or `nil` to
726
730
  # wait indefinitely.
727
731
  # @return [Toys::Utils::Exec::Result] The result object
728
732
  # @return [nil] if a timeout occurred.
729
733
  #
730
734
  def result(timeout: nil)
735
+ close_streams(:in)
731
736
  return nil if @wait_thread && !@wait_thread.join(timeout)
732
737
  @result ||= begin
733
- close_streams
738
+ close_streams(:out)
734
739
  @join_threads.each(&:join)
735
740
  Result.new(name, @captures[:out], @captures[:err], @wait_thread&.value, @exception)
736
741
  .tap { |result| @result_callback&.call(result) }
@@ -738,13 +743,13 @@ module Toys
738
743
  end
739
744
 
740
745
  ##
741
- # Close all the controller's streams.
746
+ # Close the controller's streams.
742
747
  # @private
743
748
  #
744
- def close_streams
745
- @in.close if @in && !@in.closed?
746
- @out.close if @out && !@out.closed?
747
- @err.close if @err && !@err.closed?
749
+ def close_streams(which)
750
+ @in.close if which != :out && @in && !@in.closed?
751
+ @out.close if which != :in && @out && !@out.closed?
752
+ @err.close if which != :in && @err && !@err.closed?
748
753
  self
749
754
  end
750
755
 
@@ -773,7 +778,21 @@ module Toys
773
778
  end
774
779
 
775
780
  ##
776
- # The result returned from a subcommand execution.
781
+ # The result returned from a subcommand execution. This includes the
782
+ # identifying name of the execution (if any), the result status of the
783
+ # execution, and any captured stream output.
784
+ #
785
+ # Possible result statuses are:
786
+ #
787
+ # * The process failed to start. {Result#failed?} will return true, and
788
+ # {Result#exception} will return an exception describing the failure
789
+ # (often an errno).
790
+ # * The process executed and exited with a normal exit code. Either
791
+ # {Result#success?} or {Result#error?} will return true, and
792
+ # {Result.exit_code} will return the numeric exit code.
793
+ # * The process executed but was terminated by an uncaught signal.
794
+ # {Result#signaled?} will return true, and {Result#term_signal} will
795
+ # return the numeric signal code.
777
796
  #
778
797
  class Result
779
798
  ## @private
@@ -809,11 +828,13 @@ module Toys
809
828
  attr_reader :captured_err
810
829
 
811
830
  ##
812
- # The status code object.
831
+ # The Ruby process status object, providing various information about
832
+ # the ending state of the process.
813
833
  #
814
- # Exactly one of `exception` and `status` will be non-nil.
834
+ # Exactly one of {#exception} and {#status} will be non-nil.
815
835
  #
816
- # @return [Process::Status] The status code.
836
+ # @return [Process::Status] The status, if the process was successfully
837
+ # spanwed and terminated.
817
838
  # @return [nil] if the process could not be started.
818
839
  #
819
840
  attr_reader :status
@@ -821,7 +842,7 @@ module Toys
821
842
  ##
822
843
  # The exception raised if a process couldn't be started.
823
844
  #
824
- # Exactly one of `exception` and `status` will be non-nil.
845
+ # Exactly one of {#exception} and {#status} will be non-nil.
825
846
  #
826
847
  # @return [Exception] The exception raised from process start.
827
848
  # @return [nil] if the process started successfully.
@@ -829,33 +850,76 @@ module Toys
829
850
  attr_reader :exception
830
851
 
831
852
  ##
832
- # The numeric status code.
853
+ # The numeric status code for a process that exited normally,
833
854
  #
834
- # This will be a nonzero integer if the process failed to start. That
835
- # is, `exit_code` will never be `nil`, even if `status` is `nil`.
855
+ # Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
856
+ # non-nil.
836
857
  #
837
- # @return [Integer]
858
+ # @return [Integer] the numeric status code, if the process started
859
+ # successfully and exited normally.
860
+ # @return [nil] if the process did not start successfully, or was
861
+ # terminated by an uncaught signal.
838
862
  #
839
863
  def exit_code
840
- status ? status.exitstatus : 127
864
+ status&.exitstatus
865
+ end
866
+
867
+ ##
868
+ # The numeric signal code that caused process termination.
869
+ #
870
+ # Exactly one of {#exception}, {#exit_code}, and {#term_signal} will be
871
+ # non-nil.
872
+ #
873
+ # @return [Integer] The signal that caused the process to terminate.
874
+ # @return [nil] if the process did not start successfully, or executed
875
+ # and exited with a normal exit code.
876
+ #
877
+ def term_signal
878
+ status&.termsig
879
+ end
880
+
881
+ ##
882
+ # Returns true if the subprocess failed to start, or false if the
883
+ # process was able to execute.
884
+ #
885
+ # @return [Boolean]
886
+ #
887
+ def failed?
888
+ status.nil?
889
+ end
890
+
891
+ ##
892
+ # Returns true if the subprocess terminated due to an unhandled signal,
893
+ # or false if the process failed to start or exited normally.
894
+ #
895
+ # @return [Boolean]
896
+ #
897
+ def signaled?
898
+ !term_signal.nil?
841
899
  end
842
900
 
843
901
  ##
844
- # Returns true if the subprocess terminated with a zero status.
902
+ # Returns true if the subprocess terminated with a zero status, or
903
+ # false if the process failed to start, terminated due to a signal, or
904
+ # returned a nonzero status.
845
905
  #
846
906
  # @return [Boolean]
847
907
  #
848
908
  def success?
849
- exit_code.zero?
909
+ code = exit_code
910
+ !code.nil? && code.zero?
850
911
  end
851
912
 
852
913
  ##
853
- # Returns true if the subprocess terminated with a nonzero status.
914
+ # Returns true if the subprocess terminated with a nonzero status, or
915
+ # false if the process failed to start, terminated due to a signal, or
916
+ # returned a zero status.
854
917
  #
855
918
  # @return [Boolean]
856
919
  #
857
920
  def error?
858
- !exit_code.zero?
921
+ code = exit_code
922
+ !code.nil? && !code.zero?
859
923
  end
860
924
  end
861
925
 
@@ -887,10 +951,10 @@ module Toys
887
951
  return controller if @config_opts[:background]
888
952
  begin
889
953
  @block&.call(controller)
954
+ controller.result
890
955
  ensure
891
- controller.close_streams
956
+ controller.close_streams(:both)
892
957
  end
893
- controller.result
894
958
  end
895
959
 
896
960
  private
@@ -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,16 +155,33 @@ 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"
@@ -167,9 +190,19 @@ module Toys
167
190
  end
168
191
  end
169
192
 
193
+ # @private
194
+ def self.find_gemfile(search_dir, gemfile_names: nil)
195
+ gemfile_names ||= DEFAULT_GEMFILE_NAMES
196
+ Array(gemfile_names).each do |file|
197
+ gemfile_path = ::File.join(search_dir, file)
198
+ return gemfile_path if ::File.readable?(gemfile_path)
199
+ end
200
+ nil
201
+ end
202
+
170
203
  @global_mutex = ::Monitor.new
171
204
 
172
- ## @private
205
+ # @private
173
206
  def self.synchronize(&block)
174
207
  @global_mutex.synchronize(&block)
175
208
  end
@@ -237,14 +270,6 @@ module Toys
237
270
  raise ActivationFailedError, err.message
238
271
  end
239
272
 
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
273
  def configure_gemfile(gemfile_path)
249
274
  old_path = ::ENV["BUNDLE_GEMFILE"]
250
275
  if old_path
@@ -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.0
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-08-21 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.0/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.0
76
76
  post_install_message:
77
77
  rdoc_options: []
78
78
  require_paths: