toys-core 0.14.7 → 0.15.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.
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module Toys
6
4
  ##
7
5
  # A ToolDefinition describes a single command that can be invoked using Toys.
@@ -18,12 +16,12 @@ module Toys
18
16
  ##
19
17
  # Create a completion given configuration options.
20
18
  #
21
- # @param complete_subtools [Boolean] Whether to complete subtool names
22
- # @param include_hidden_subtools [Boolean] Whether to include hidden
19
+ # @param complete_subtools [true,false] Whether to complete subtool names
20
+ # @param include_hidden_subtools [true,false] Whether to include hidden
23
21
  # subtools (i.e. those beginning with an underscore)
24
- # @param complete_args [Boolean] Whether to complete positional args
25
- # @param complete_flags [Boolean] Whether to complete flag names
26
- # @param complete_flag_values [Boolean] Whether to complete flag values
22
+ # @param complete_args [true,false] Whether to complete positional args
23
+ # @param complete_flags [true,false] Whether to complete flag names
24
+ # @param complete_flag_values [true,false] Whether to complete flag values
27
25
  # @param delegation_target [Array<String>,nil] Delegation target, or
28
26
  # `nil` if none.
29
27
  #
@@ -41,7 +39,7 @@ module Toys
41
39
 
42
40
  ##
43
41
  # Whether to complete subtool names
44
- # @return [Boolean]
42
+ # @return [true,false]
45
43
  #
46
44
  def complete_subtools?
47
45
  @complete_subtools
@@ -49,7 +47,7 @@ module Toys
49
47
 
50
48
  ##
51
49
  # Whether to include hidden subtools
52
- # @return [Boolean]
50
+ # @return [true,false]
53
51
  #
54
52
  def include_hidden_subtools?
55
53
  @include_hidden_subtools
@@ -57,7 +55,7 @@ module Toys
57
55
 
58
56
  ##
59
57
  # Whether to complete flags
60
- # @return [Boolean]
58
+ # @return [true,false]
61
59
  #
62
60
  def complete_flags?
63
61
  @complete_flags
@@ -65,7 +63,7 @@ module Toys
65
63
 
66
64
  ##
67
65
  # Whether to complete positional args
68
- # @return [Boolean]
66
+ # @return [true,false]
69
67
  #
70
68
  def complete_args?
71
69
  @complete_args
@@ -73,7 +71,7 @@ module Toys
73
71
 
74
72
  ##
75
73
  # Whether to complete flag values
76
- # @return [Boolean]
74
+ # @return [true,false]
77
75
  #
78
76
  def complete_flag_values?
79
77
  @complete_flag_values
@@ -205,7 +203,7 @@ module Toys
205
203
  #
206
204
  # The following settings are supported:
207
205
  #
208
- # * `propagate_helper_methods` (_Boolean_) - Whether subtools should
206
+ # * `propagate_helper_methods` (_boolean_) - Whether subtools should
209
207
  # inherit methods defined by parent tools. Defaults to `false`.
210
208
  #
211
209
  class Settings < ::Toys::Settings
@@ -273,7 +271,8 @@ module Toys
273
271
  @includes_modules = false
274
272
  @custom_context_directory = nil
275
273
 
276
- @interrupt_handler = nil
274
+ @run_handler = :run
275
+ @signal_handlers = {}
277
276
  @usage_error_handler = nil
278
277
  @delegate_target = nil
279
278
 
@@ -283,7 +282,7 @@ module Toys
283
282
  ##
284
283
  # Settings for this tool
285
284
  #
286
- # @return [Toys::Tool::Settings]
285
+ # @return [Toys::ToolDefinition::Settings]
287
286
  #
288
287
  attr_reader :settings
289
288
 
@@ -454,19 +453,33 @@ module Toys
454
453
  attr_reader :completion
455
454
 
456
455
  ##
457
- # The interrupt handler.
456
+ # The run handler.
457
+ #
458
+ # This handler is called to run the tool. Normally it is a method name,
459
+ # represented by a symbol. (The default is `:run`.) It can be set to a
460
+ # different method name, or to a proc that will be called with `self` set
461
+ # to the tool context. Either way, it takes no arguments. The run handler
462
+ # can also be explicitly set to `nil` indicating a non-runnable tool;
463
+ # however, typically a tool is made non-runnable simply by leaving the run
464
+ # handler set to `:run` and not defining the method.
458
465
  #
459
- # @return [Proc] The interrupt handler proc
460
- # @return [Symbol] The name of a method to call
461
- # @return [nil] if there is no interrupt handler
466
+ # @return [Proc] if the run handler is defined as a Proc
467
+ # @return [Symbol] if the run handler is defined as a method
468
+ # @return [nil] if the tool is explicitly made non-runnable
462
469
  #
463
- attr_reader :interrupt_handler
470
+ attr_reader :run_handler
464
471
 
465
472
  ##
466
473
  # The usage error handler.
467
474
  #
468
- # @return [Proc] The usage error handler proc
469
- # @return [Symbol] The name of a method to call
475
+ # This handler is called when at least one usage error is detected during
476
+ # argument parsing, and is called instead of the `run` method. It can be
477
+ # specified as a Proc, or a Symbol indicating a method to call. It
478
+ # optionally takes an array of {Toys::ArgParser::UsageError} as the sole
479
+ # argument.
480
+ #
481
+ # @return [Proc] if the usage error handler is defined as a Proc
482
+ # @return [Symbol] if the user error handler is defined as a method
470
483
  # @return [nil] if there is no usage error handler
471
484
  #
472
485
  attr_reader :usage_error_handler
@@ -498,9 +511,37 @@ module Toys
498
511
  full_name.join(" ")
499
512
  end
500
513
 
514
+ ##
515
+ # Return the signal handler for the given signal.
516
+ #
517
+ # This handler is called when the given signal is received, immediately
518
+ # taking over the execution as if it were the new run handler. The signal
519
+ # handler can be specified as a Proc, or a Symbol indicating a method to
520
+ # call. It optionally takes the `SignalException` as the sole argument.
521
+ #
522
+ # @param signal [Integer,String,Symbol] The signal number or name
523
+ # @return [Proc] if the signal handler is defined as a Proc
524
+ # @return [Symbol] if the signal handler is defined as a method
525
+ # @return [nil] if there is no handler for the given signal
526
+ #
527
+ def signal_handler(signal)
528
+ @signal_handlers[canonicalize_signal(signal)]
529
+ end
530
+
531
+ ##
532
+ # Return the interrupt handler. This is equivalent to `signal_handler(2)`.
533
+ #
534
+ # @return [Proc] if the interrupt signal handler is defined as a Proc
535
+ # @return [Symbol] if the interrupt signal handler is defined as a method
536
+ # @return [nil] if there is no handler for the interrupt signals
537
+ #
538
+ def interrupt_handler
539
+ signal_handler(2)
540
+ end
541
+
501
542
  ##
502
543
  # Returns true if this tool is a root tool.
503
- # @return [Boolean]
544
+ # @return [true,false]
504
545
  #
505
546
  def root?
506
547
  full_name.empty?
@@ -508,23 +549,38 @@ module Toys
508
549
 
509
550
  ##
510
551
  # Returns true if this tool is marked as runnable.
511
- # @return [Boolean]
552
+ # @return [true,false]
512
553
  #
513
554
  def runnable?
514
- tool_class.public_instance_methods(false).include?(:run)
555
+ @run_handler.is_a?(::Symbol) &&
556
+ tool_class.public_instance_methods(false).include?(@run_handler) ||
557
+ @run_handler.is_a?(::Proc)
515
558
  end
516
559
 
517
560
  ##
518
- # Returns true if this tool handles interrupts.
519
- # @return [Boolean]
561
+ # Returns true if this tool handles interrupts. This is equivalent to
562
+ # `handles_signal?(2)`.
563
+ #
564
+ # @return [true,false]
520
565
  #
521
566
  def handles_interrupts?
522
- !interrupt_handler.nil?
567
+ handles_signal?(2)
568
+ end
569
+
570
+ ##
571
+ # Returns true if this tool handles the given signal.
572
+ #
573
+ # @param signal [Integer,String,Symbol] The signal number or name
574
+ # @return [true,false]
575
+ #
576
+ def handles_signal?(signal)
577
+ signal = canonicalize_signal(signal)
578
+ !@signal_handlers[signal].nil?
523
579
  end
524
580
 
525
581
  ##
526
582
  # Returns true if this tool handles usage errors.
527
- # @return [Boolean]
583
+ # @return [true,false]
528
584
  #
529
585
  def handles_usage_errors?
530
586
  !usage_error_handler.nil?
@@ -532,7 +588,7 @@ module Toys
532
588
 
533
589
  ##
534
590
  # Returns true if this tool has at least one included module.
535
- # @return [Boolean]
591
+ # @return [true,false]
536
592
  #
537
593
  def includes_modules?
538
594
  @includes_modules
@@ -540,7 +596,7 @@ module Toys
540
596
 
541
597
  ##
542
598
  # Returns true if there is a specific description set for this tool.
543
- # @return [Boolean]
599
+ # @return [true,false]
544
600
  #
545
601
  def includes_description?
546
602
  !long_desc.empty? || !desc.empty?
@@ -549,7 +605,7 @@ module Toys
549
605
  ##
550
606
  # Returns true if at least one flag or positional argument is defined
551
607
  # for this tool.
552
- # @return [Boolean]
608
+ # @return [true,false]
553
609
  #
554
610
  def includes_arguments?
555
611
  !default_data.empty? || !flags.empty? ||
@@ -559,7 +615,7 @@ module Toys
559
615
 
560
616
  ##
561
617
  # Returns true if this tool has any definition information.
562
- # @return [Boolean]
618
+ # @return [true,false]
563
619
  #
564
620
  def includes_definition?
565
621
  includes_arguments? || runnable? || argument_parsing_disabled? ||
@@ -568,7 +624,7 @@ module Toys
568
624
 
569
625
  ##
570
626
  # Returns true if this tool's definition has been finished and is locked.
571
- # @return [Boolean]
627
+ # @return [true,false]
572
628
  #
573
629
  def definition_finished?
574
630
  @definition_finished
@@ -576,7 +632,7 @@ module Toys
576
632
 
577
633
  ##
578
634
  # Returns true if this tool has disabled argument parsing.
579
- # @return [Boolean]
635
+ # @return [true,false]
580
636
  #
581
637
  def argument_parsing_disabled?
582
638
  @disable_argument_parsing
@@ -584,7 +640,7 @@ module Toys
584
640
 
585
641
  ##
586
642
  # Returns true if this tool enforces flags before args.
587
- # @return [Boolean]
643
+ # @return [true,false]
588
644
  #
589
645
  def flags_before_args_enforced?
590
646
  @enforce_flags_before_args
@@ -592,7 +648,7 @@ module Toys
592
648
 
593
649
  ##
594
650
  # Returns true if this tool requires exact flag matches.
595
- # @return [Boolean]
651
+ # @return [true,false]
596
652
  #
597
653
  def exact_flag_match_required?
598
654
  @require_exact_flag_match
@@ -867,7 +923,7 @@ module Toys
867
923
  # Enforce that flags must come before args for this tool.
868
924
  # You may disable enforcement by passoing `false` for the state.
869
925
  #
870
- # @param state [Boolean]
926
+ # @param state [true,false]
871
927
  # @return [self]
872
928
  #
873
929
  def enforce_flags_before_args(state = true)
@@ -885,7 +941,7 @@ module Toys
885
941
  # Require that flags must match exactly. (If false, flags can match an
886
942
  # unambiguous substring.)
887
943
  #
888
- # @param state [Boolean]
944
+ # @param state [true,false]
889
945
  # @return [self]
890
946
  #
891
947
  def require_exact_flag_match(state = true)
@@ -919,10 +975,10 @@ module Toys
919
975
  # formats. Defaults to the empty array.
920
976
  # @param name [String,Symbol,nil] The name of the group, or nil for no
921
977
  # name.
922
- # @param report_collisions [Boolean] If `true`, raise an exception if a
978
+ # @param report_collisions [true,false] If `true`, raise an exception if a
923
979
  # the given name is already taken. If `false`, ignore. Default is
924
980
  # `true`.
925
- # @param prepend [Boolean] If `true`, prepend rather than append the
981
+ # @param prepend [true,false] If `true`, prepend rather than append the
926
982
  # group to the list. Default is `false`.
927
983
  # @return [self]
928
984
  #
@@ -977,7 +1033,7 @@ module Toys
977
1033
  # @param complete_values [Object] A specifier for shell tab completion
978
1034
  # for flag values associated with this flag. Pass any spec
979
1035
  # recognized by {Toys::Completion.create}.
980
- # @param report_collisions [Boolean] Raise an exception if a flag is
1036
+ # @param report_collisions [true,false] Raise an exception if a flag is
981
1037
  # requested that is already in use or marked as disabled. Default is
982
1038
  # true.
983
1039
  # @param group [Toys::FlagGroup,String,Symbol,nil] Group for
@@ -1147,33 +1203,65 @@ module Toys
1147
1203
  end
1148
1204
 
1149
1205
  ##
1150
- # Set the run handler block
1206
+ # Set the run handler.
1151
1207
  #
1152
- # @param proc [Proc] The runnable block
1208
+ # This handler is called to run the tool. Normally it is a method name,
1209
+ # represented by a symbol. (The default is `:run`.) It can be set to a
1210
+ # different method name, or to a proc that will be called with `self` set
1211
+ # to the tool context. Either way, it takes no arguments. The run handler
1212
+ # can also be explicitly set to `nil` indicating a non-runnable tool;
1213
+ # however, typically a tool is made non-runnable simply by leaving the run
1214
+ # handler set to `:run` and not defining the method.
1153
1215
  #
1154
- def run_handler=(proc)
1216
+ # @param handler [Proc,Symbol,nil] the run handler
1217
+ #
1218
+ def run_handler=(handler)
1155
1219
  check_definition_state(is_method: true)
1156
- tool_class.class_eval do
1157
- define_method(:run, &proc)
1220
+ if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
1221
+ raise ToolDefinitionError, "Run handler must be a proc or symbol"
1158
1222
  end
1223
+ @run_handler = handler
1159
1224
  end
1160
1225
 
1161
1226
  ##
1162
- # Set the interrupt handler.
1227
+ # Set the interrupt handler. This is equivalent to calling
1228
+ # {#set_signal_handler} for the `SIGINT` signal.
1163
1229
  #
1164
- # @param handler [Proc,Symbol] The interrupt handler
1230
+ # @param handler [Proc,Symbol] The interrupt signal handler
1165
1231
  #
1166
1232
  def interrupt_handler=(handler)
1233
+ set_signal_handler(2, handler)
1234
+ end
1235
+
1236
+ ##
1237
+ # Set the handler for the given signal.
1238
+ #
1239
+ # This handler is called when the given signal is received, immediately
1240
+ # taking over the execution as if it were the new `run` method. The signal
1241
+ # handler can be specified as a Proc, or a Symbol indicating a method to
1242
+ # call. It optionally takes the `SignalException` as the sole argument.
1243
+ #
1244
+ # @param signal [Integer,String,Symbol] The signal number or name
1245
+ # @param handler [Proc,Symbol] The signal handler
1246
+ #
1247
+ def set_signal_handler(signal, handler)
1167
1248
  check_definition_state(is_method: true)
1168
1249
  if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
1169
- raise ToolDefinitionError, "Interrupt handler must be a proc or symbol"
1250
+ raise ToolDefinitionError, "Signal handler must be a proc or symbol"
1170
1251
  end
1171
- @interrupt_handler = handler
1252
+ signal = canonicalize_signal(signal)
1253
+ @signal_handlers[signal] = handler
1172
1254
  end
1173
1255
 
1174
1256
  ##
1175
1257
  # Set the usage error handler.
1176
1258
  #
1259
+ # This handler is called when at least one usage error is detected during
1260
+ # argument parsing, and is called instead of the `run` method. It can be
1261
+ # specified as a Proc, or a Symbol indicating a method to call. It
1262
+ # optionally takes an array of {Toys::ArgParser::UsageError} as the sole
1263
+ # argument.
1264
+ #
1177
1265
  # @param handler [Proc,Symbol] The usage error handler
1178
1266
  #
1179
1267
  def usage_error_handler=(handler)
@@ -1374,14 +1462,14 @@ module Toys
1374
1462
  name = walk_context[::Toys::Context::Key::TOOL_NAME]
1375
1463
  path << name.join(" ").inspect
1376
1464
  if name == target
1377
- raise "Delegation loop: #{path.join(' <- ')}"
1465
+ raise ToolDefinitionError, "Delegation loop: #{path.join(' <- ')}"
1378
1466
  end
1379
1467
  walk_context = walk_context[::Toys::Context::Key::DELEGATED_FROM]
1380
1468
  end
1381
1469
  cli = self[::Toys::Context::Key::CLI]
1382
1470
  cli.loader.load_for_prefix(target)
1383
1471
  unless cli.loader.tool_defined?(target)
1384
- raise "Delegate target not found: \"#{target.join(' ')}\""
1472
+ raise ToolDefinitionError, "Delegate target not found: \"#{target.join(' ')}\""
1385
1473
  end
1386
1474
  exit(cli.run(target + self[::Toys::Context::Key::ARGS], delegated_from: self))
1387
1475
  end
@@ -1400,5 +1488,18 @@ module Toys
1400
1488
  raise ToolDefinitionError, "Unknown completion: #{name.inspect}" if completion.nil?
1401
1489
  completion
1402
1490
  end
1491
+
1492
+ def canonicalize_signal(signal)
1493
+ case signal
1494
+ when ::String, ::Symbol
1495
+ sigstr = signal.to_s
1496
+ sigstr = sigstr[3..-1] if sigstr.start_with?("SIG")
1497
+ signo = ::Signal.list[sigstr]
1498
+ return signo if signo
1499
+ when ::Integer
1500
+ return signal if ::Signal.signame(signal)
1501
+ end
1502
+ raise ::ArgumentError, "Unknown signal: #{signal}"
1503
+ end
1403
1504
  end
1404
1505
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "shellwords"
4
-
5
3
  module Toys
6
4
  module Utils
7
5
  ##
@@ -21,6 +19,7 @@ module Toys
21
19
  # @param cli [Toys::CLI] The CLI.
22
20
  #
23
21
  def initialize(cli)
22
+ require "shellwords"
24
23
  @cli = cli
25
24
  end
26
25
 
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rbconfig"
4
- require "logger"
5
- require "shellwords"
6
-
7
3
  module Toys
8
4
  module Utils
9
5
  ##
@@ -16,6 +12,27 @@ module Toys
16
12
  # This class is not loaded by default. Before using it directly, you should
17
13
  # `require "toys/utils/exec"`
18
14
  #
15
+ # ### The exec service
16
+ #
17
+ # The main entrypoint class is this one, {Toys::Utils::Exec}. It's a
18
+ # "service" object that provides functionality, primarily methods that
19
+ # spawn processes. Create it like any object:
20
+ #
21
+ # require "toys/utils/exec"
22
+ # exec_service = Toys::Utils::Exec.new
23
+ #
24
+ # There are two "primitive" functions: {#exec} and {#exec_proc}. The {#exec}
25
+ # method spawns an operating system process specified by an executable and
26
+ # a set of arguments. The {#exec_proc} method takes a `Proc` and forks a
27
+ # Ruby process. Both of these can be heavily configured with stream
28
+ # handling, result handling, and numerous other options described below.
29
+ # The class also provides convenience methods for common cases such as
30
+ # spawning a Ruby process, spawning a shell script, or capturing output.
31
+ #
32
+ # The exec service class also stores default configuration that it applies
33
+ # to processes it spawns. You can set these defaults when constructing the
34
+ # service class, or at any time by calling {#configure_defaults}.
35
+ #
19
36
  # ### Controlling processes
20
37
  #
21
38
  # A process can be started in the *foreground* or the *background*. If you
@@ -24,7 +41,7 @@ module Toys
24
41
  # If you start a background process, its streams will be redirected to null
25
42
  # by default, and control will be returned to you immediately.
26
43
  #
27
- # When a process is running, you can control it using a
44
+ # While a process is running, you can control it using a
28
45
  # {Toys::Utils::Exec::Controller} object. Use a controller to interact with
29
46
  # the process's input and output streams, send it signals, or wait for it
30
47
  # to complete.
@@ -245,6 +262,9 @@ module Toys
245
262
  # for a description of the options.
246
263
  #
247
264
  def initialize(**opts, &block)
265
+ require "rbconfig"
266
+ require "logger"
267
+ require "stringio"
248
268
  @default_opts = Opts.new(&block).add(opts)
249
269
  end
250
270
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "monitor"
4
- require "rubygems"
5
4
 
6
5
  module Toys
7
6
  module Utils
@@ -123,6 +122,7 @@ module Toys
123
122
  output: nil,
124
123
  suppress_confirm: nil,
125
124
  default_confirm: nil)
125
+ require "rubygems"
126
126
  @default_confirm = default_confirm || default_confirm.nil? ? true : false
127
127
  @on_missing = on_missing ||
128
128
  if suppress_confirm
@@ -304,6 +304,9 @@ module Toys
304
304
  end
305
305
 
306
306
  def setup_bundle(gemfile_path, groups: nil, retries: nil)
307
+ # Lock the bundler version, preventing bundler's SelfManager from
308
+ # installing a different bundler and taking over the process.
309
+ ::ENV["BUNDLER_VERSION"] = ::Bundler::VERSION
307
310
  check_gemfile_compatibility(gemfile_path)
308
311
  groups = Array(groups)
309
312
  modified_gemfile_path = create_modified_gemfile(gemfile_path)
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
- require "fileutils"
5
- require "json"
6
- require "toys/compat"
7
- require "toys/utils/exec"
8
- require "toys/utils/xdg"
9
-
10
3
  module Toys
11
4
  module Utils
12
5
  ##
@@ -288,6 +281,11 @@ module Toys
288
281
  # a specific directory in the user's XDG cache.
289
282
  #
290
283
  def initialize(cache_dir: nil)
284
+ require "digest"
285
+ require "fileutils"
286
+ require "json"
287
+ require "toys/compat"
288
+ require "toys/utils/exec"
291
289
  @cache_dir = ::File.expand_path(cache_dir || default_cache_dir)
292
290
  @exec = Utils::Exec.new(out: :capture, err: :capture)
293
291
  end
@@ -485,6 +483,7 @@ module Toys
485
483
  end
486
484
 
487
485
  def default_cache_dir
486
+ require "toys/utils/xdg"
488
487
  ::File.join(XDG.new.cache_home, "toys", "git")
489
488
  end
490
489
 
@@ -351,6 +351,7 @@ module Toys
351
351
  def initialize(tool, executable_name, delegates, subtools, search_term,
352
352
  show_source_path, separate_sources, indent, indent2, wrap_width, styled)
353
353
  require "toys/utils/terminal"
354
+ require "stringio"
354
355
  @tool = tool
355
356
  @executable_name = executable_name
356
357
  @delegates = delegates
@@ -667,6 +668,7 @@ module Toys
667
668
  def initialize(tool, subtools, recursive, search_term, separate_sources,
668
669
  indent, wrap_width, styled)
669
670
  require "toys/utils/terminal"
671
+ require "stringio"
670
672
  @tool = tool
671
673
  @subtools = subtools
672
674
  @recursive = recursive
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "toys/utils/exec"
4
-
5
3
  module Toys
6
4
  module Utils
7
5
  ##
@@ -150,7 +148,10 @@ module Toys
150
148
  # @private
151
149
  #
152
150
  def default_exec_service
153
- @default_exec_service ||= Exec.new
151
+ @default_exec_service ||= begin
152
+ require "toys/utils/exec"
153
+ Utils::Exec.new
154
+ end
154
155
  end
155
156
  end
156
157
  end