toys-core 0.10.2 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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