toys-core 0.10.2 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5ae179649a0653db765b024bf25809d9e2cac6eb97aa3f1058be9ac59e9837c
4
- data.tar.gz: eabbdaf0d81b86ab64674fbda0728ca361d66e9d69cfcd3ce76670f44b828d9b
3
+ metadata.gz: 1f26779ccf17122c41c4c52f5d16ca9630e9936e8251e9fb8ee94e2f35902ed5
4
+ data.tar.gz: 9f192532b5a5e384662881ce422422d81275ae5993721d98c8aec3a77df05bae
5
5
  SHA512:
6
- metadata.gz: e1312418f9bd568f880580faed87e55100800def6bb34cf159e26e342949fefceec6a73584af62569775a29921d923618a8cbc05f113d1797103c0133c1b7b2d
7
- data.tar.gz: 335c0424407723fc242d9ea3ee0153bc9213b1b86e36a347106a96262aeb423c2aaa91cc71dcce72b4179eba5c40df1da112c87982c1543af337276db85bfb86
6
+ metadata.gz: 0a265cf80f60bfe2d6cebb4ba9a3674aa7060a47187b77c5d8e879cde28480d7e09dc4dc365944e6fcba09ddb817553a4ce2e0f9db3ecab93d1e06316ba5fb6e
7
+ data.tar.gz: 151b37988b3dc14a06e2ee4cf85c941dcb46b345d44d66940a307d9968eccb6ca5d52eb386eaa87a07582968e9e5693b8c89149d314e2f620022a1f1bf6226fc
@@ -1,5 +1,36 @@
1
1
  # Release History
2
2
 
3
+ ### 0.11.1 / 2020-08-24
4
+
5
+ * DOCS: Minor documentation tweaks.
6
+
7
+ ### 0.11.0 / 2020-08-21
8
+
9
+ * ADDED: The load path can be truncated using the `truncate_load_path!` directive.
10
+ * IMPROVED: Generated help for delegates now includes the information for the target tool, plus subtools of the delegate.
11
+ * IMPROVED: The `:bundler` mixin searches for `gems.rb` and `.gems.rb` in addition to `Gemfile`.
12
+ * IMPROVED: The `:budnler` mixin can load a specific Gemfile path.
13
+ * FIXED: The loader can now find data and lib directories at the root level of a Toys directory.
14
+ * FIXED: Exec::Result correctly reports processes that terminated due to signals.
15
+ * FIXED: Fixed a rare Exec capture failure that resulted from a race condition when closing streams.
16
+
17
+ ### 0.10.5 / 2020-07-18
18
+
19
+ * IMPROVED: The bundler mixin silences bundler output during bundle setup.
20
+ * 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.
21
+ * IMPROVED: The bundler mixin automatically updates the bundle if install fails (typically because a transitive dependency has been explicitly updated.)
22
+ * FIXED: Some cases of transitive dependency handling by the bundler mixin.
23
+ * FIXED: Fixed a crash when computing suggestions, when running with a bundle on Ruby 2.6 or earlier.
24
+
25
+ ### 0.10.4 / 2020-07-11
26
+
27
+ * IMPROVED: Bundler integration can now handle Toys itself being in the bundle, as long as the version requirements cover the running Toys version.
28
+ * IMPROVED: Passing `static: true` to the `:bundler` mixin installs the bundle at definition rather than execution time.
29
+
30
+ ### 0.10.3 / 2020-07-04
31
+
32
+ * FIXED: The `exec_separate_tool` method in the `:exec` mixin no longer throws ENOEXEC on Windows.
33
+
3
34
  ### 0.10.2 / 2020-07-03
4
35
 
5
36
  * FIXED: The load path no longer loses the toys and toys-core directories after a bundle install.
data/README.md CHANGED
@@ -258,7 +258,7 @@ itself. However, the `toys-core` gem is a dependency, and your users will need
258
258
  to have it installed. You could alleviate this by wrapping your executable in a
259
259
  gem that can declare `toys-core` as a dependency explicitly.
260
260
 
261
- The [examples directory](https://github.com/dazuma/toys/tree/master/toys-core/examples)
261
+ The [examples directory](https://github.com/dazuma/toys/tree/main/toys-core/examples)
262
262
  includes a few simple examples that you can use as a starting point.
263
263
 
264
264
  To experiment with the examples, clone the Toys repo from GitHub:
@@ -272,7 +272,7 @@ Navigate to the simple-gem example:
272
272
 
273
273
  This example wraps the simple "greet" executable that we
274
274
  [covered earlier](#Add_some_functionality) in a gem. You can see the
275
- [executable file](https://github.com/dazuma/toys/tree/master/toys-core/examples/simple-gem/bin/toys-core-simple-example)
275
+ [executable file](https://github.com/dazuma/toys/tree/main/toys-core/examples/simple-gem/bin/toys-core-simple-example)
276
276
  in the bin directory.
277
277
 
278
278
  Try it out by building and installing the gem. From the `examples/simple-gem`
@@ -297,16 +297,16 @@ break it up into multiple files. The multi-file gem example demonstrates this.
297
297
  $ cd ../multi-file-gem
298
298
 
299
299
  This executable's implementation resides in its
300
- [lib directory](https://github.com/dazuma/toys/tree/master/toys-core/examples/multi-file-gem/lib),
300
+ [lib directory](https://github.com/dazuma/toys/tree/main/toys-core/examples/multi-file-gem/lib),
301
301
  a technique that may be familiar to writers of command line executables. More
302
302
  interestingly, the tools themselves are no longer defined in a block passed to
303
303
  the CLI object, but have been moved into a separate
304
- ["tools" directory](https://github.com/dazuma/toys/tree/master/toys-core/examples/multi-file-gem/tools).
304
+ ["tools" directory](https://github.com/dazuma/toys/tree/main/toys-core/examples/multi-file-gem/tools).
305
305
  This directory has the same structure and supports the same features that are
306
306
  available when writing complex sets of tools in a `.toys` directory. You then
307
307
  configure the CLI object to look in this directory for its tools definitions,
308
308
  as you can see in
309
- [the code](https://github.com/dazuma/toys/tree/master/toys-core/examples/multi-file-gem/lib/toys-core-multi-gem-example.rb).
309
+ [the code](https://github.com/dazuma/toys/tree/main/toys-core/examples/multi-file-gem/lib/toys-core-multi-gem-example.rb).
310
310
 
311
311
  Try it out now. From the `examples/multi-file-gem` directory, run:
312
312
 
@@ -1,4 +1,6 @@
1
+ <!--
1
2
  # @title Toys-Core User Guide
3
+ -->
2
4
 
3
5
  # Toys-Core User Guide
4
6
 
@@ -498,7 +498,7 @@ module Toys
498
498
  # well-known acceptor.
499
499
  #
500
500
  # @param spec [Object] See the description for recognized values.
501
- # @param options [Hash] Additional options to pass to the completion.
501
+ # @param options [Hash] Additional options to pass to the acceptor.
502
502
  # @param block [Proc] See the description for recognized forms.
503
503
  # @return [Toys::Acceptor::Base,Proc]
504
504
  #
@@ -16,21 +16,30 @@ module Toys
16
16
  ::RUBY_PLATFORM == "java"
17
17
  end
18
18
 
19
+ # @private
20
+ def self.windows?
21
+ ::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
22
+ end
23
+
19
24
  # @private
20
25
  def self.allow_fork?
21
- !jruby? && ::RbConfig::CONFIG["host_os"] !~ /mswin/
26
+ !jruby? && !windows?
22
27
  end
23
28
 
24
29
  # @private
25
30
  def self.supports_suggestions?
26
31
  unless defined?(@supports_suggestions)
27
- require "rubygems"
28
32
  begin
29
33
  require "did_you_mean"
30
- @supports_suggestions = defined?(::DidYouMean::SpellChecker)
31
34
  rescue ::LoadError
32
- @supports_suggestions = false
35
+ require "rubygems"
36
+ begin
37
+ require "did_you_mean"
38
+ rescue ::LoadError
39
+ # Oh well, it's not available
40
+ end
33
41
  end
42
+ @supports_suggestions = defined?(::DidYouMean::SpellChecker)
34
43
  end
35
44
  @supports_suggestions
36
45
  end
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.10.2"
12
+ VERSION = "0.11.1"
13
13
  end
14
14
 
15
15
  ## @private deprecated
@@ -75,8 +75,8 @@ module Toys
75
75
  # string (e.g. `"foo"`) is taken as the value. Otherwise, the
76
76
  # following argument is taken as the value (e.g. for `--abc foo`, the
77
77
  # value is set to `"foo"`.) The following argument is treated as the
78
- # value even if it looks like a flag (e.g. `--abc --abc` causes the
79
- # string `"--abc"` to be taken as the value.)
78
+ # value even if it looks like a flag (e.g. `--abc --def` causes the
79
+ # string `"--def"` to be taken as the value.)
80
80
  # * `--abc[=VAL]` : A long flag that takes an optional value. If this
81
81
  # argument appears with a value attached (e.g. `--abc=foo`), the
82
82
  # attached string (e.g. `"foo"`) is taken as the value. Otherwise,
@@ -79,8 +79,8 @@ module Toys
79
79
  # string (e.g. `"foo"`) is taken as the value. Otherwise, the
80
80
  # following argument is taken as the value (e.g. for `--abc foo`, the
81
81
  # value is set to `"foo"`.) The following argument is treated as the
82
- # value even if it looks like a flag (e.g. `--abc --abc` causes the
83
- # string `"--abc"` to be taken as the value.)
82
+ # value even if it looks like a flag (e.g. `--abc --def` causes the
83
+ # string `"--def"` to be taken as the value.)
84
84
  # * `--abc[=VAL]` : A long flag that takes an optional value. If this
85
85
  # argument appears with a value attached (e.g. `--abc=foo`), the
86
86
  # attached string (e.g. `"foo"`) is taken as the value. Otherwise,
@@ -814,8 +814,8 @@ module Toys
814
814
  # string (e.g. `"foo"`) is taken as the value. Otherwise, the
815
815
  # following argument is taken as the value (e.g. for `--abc foo`, the
816
816
  # value is set to `"foo"`.) The following argument is treated as the
817
- # value even if it looks like a flag (e.g. `--abc --abc` causes the
818
- # string `"--abc"` to be taken as the value.)
817
+ # value even if it looks like a flag (e.g. `--abc --def` causes the
818
+ # string `"--def"` to be taken as the value.)
819
819
  # * `--abc[=VAL]` : A long flag that takes an optional value. If this
820
820
  # argument appears with a value attached (e.g. `--abc=foo`), the
821
821
  # attached string (e.g. `"foo"`) is taken as the value. Otherwise,
@@ -884,7 +884,8 @@ module Toys
884
884
  # @param flags [String...] The flags in OptionParser format.
885
885
  # @param accept [Object] An acceptor that validates and/or converts the
886
886
  # value. You may provide either the name of an acceptor you have
887
- # defined, or one of the default acceptors provided by OptionParser.
887
+ # defined, one of the default acceptors provided by OptionParser, or
888
+ # any other specification recognized by {Toys::Acceptor.create}.
888
889
  # Optional. If not specified, accepts any value as a string.
889
890
  # @param default [Object] The default value. This is the value that will
890
891
  # be set in the context if this flag is not provided on the command
@@ -979,7 +980,8 @@ module Toys
979
980
  # the execution context.
980
981
  # @param accept [Object] An acceptor that validates and/or converts the
981
982
  # value. You may provide either the name of an acceptor you have
982
- # defined, or one of the default acceptors provided by OptionParser.
983
+ # defined, one of the default acceptors provided by OptionParser, or
984
+ # any other specification recognized by {Toys::Acceptor.create}.
983
985
  # Optional. If not specified, accepts any value as a string.
984
986
  # @param complete [Object] A specifier for shell tab completion for
985
987
  # values of this arg. This is the empty completion by default. To
@@ -1050,7 +1052,8 @@ module Toys
1050
1052
  # line. Defaults to `nil`.
1051
1053
  # @param accept [Object] An acceptor that validates and/or converts the
1052
1054
  # value. You may provide either the name of an acceptor you have
1053
- # defined, or one of the default acceptors provided by OptionParser.
1055
+ # defined, one of the default acceptors provided by OptionParser, or
1056
+ # any other specification recognized by {Toys::Acceptor.create}.
1054
1057
  # Optional. If not specified, accepts any value as a string.
1055
1058
  # @param complete [Object] A specifier for shell tab completion for
1056
1059
  # values of this arg. This is the empty completion by default. To
@@ -1121,7 +1124,8 @@ module Toys
1121
1124
  # command line. Defaults to the empty array `[]`.
1122
1125
  # @param accept [Object] An acceptor that validates and/or converts the
1123
1126
  # value. You may provide either the name of an acceptor you have
1124
- # defined, or one of the default acceptors provided by OptionParser.
1127
+ # defined, one of the default acceptors provided by OptionParser, or
1128
+ # any other specification recognized by {Toys::Acceptor.create}.
1125
1129
  # Optional. If not specified, accepts any value as a string.
1126
1130
  # @param complete [Object] A specifier for shell tab completion for
1127
1131
  # values of this arg. This is the empty completion by default. To
@@ -1619,6 +1623,23 @@ module Toys
1619
1623
  self
1620
1624
  end
1621
1625
 
1626
+ ##
1627
+ # Remove lower-priority sources from the load path. This prevents lower-
1628
+ # priority sources (such as Toys files from parent or global directories)
1629
+ # from executing or defining tools.
1630
+ #
1631
+ # This works only if no such sources have already loaded yet.
1632
+ #
1633
+ # @raise [Toys::ToolDefinitionError] if any lower-priority tools have
1634
+ # already been loaded.
1635
+ #
1636
+ def truncate_load_path!
1637
+ unless @__loader.stop_loading_at_priority(@__priority)
1638
+ raise ToolDefinitionError,
1639
+ "Cannot truncate load path because tools have already been loaded"
1640
+ end
1641
+ end
1642
+
1622
1643
  ##
1623
1644
  # Determines whether the current Toys version satisfies the given
1624
1645
  # 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