toys-core 0.14.7 → 0.15.1

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