toys-core 0.14.7 → 0.15.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.
@@ -10,11 +10,24 @@ module Toys
10
10
  # tool DSL itself, so you can also ensure a gem is present when defining a
11
11
  # tool.
12
12
  #
13
- # You may make these methods available to your tool by including the
14
- # following directive in your tool configuration:
13
+ # ### Usage
14
+ #
15
+ # Make these methods available to your tool by including this mixin in your
16
+ # tool:
15
17
  #
16
18
  # include :gems
17
19
  #
20
+ # You can then call the mixin method {#gem} to ensure that a gem is
21
+ # installed and in the load path. For example:
22
+ #
23
+ # tool "my_tool" do
24
+ # include :gems
25
+ # gem "nokogiri", "~> 1.15"
26
+ # def run
27
+ # # Do stuff with Nokogiri
28
+ # end
29
+ # end
30
+ #
18
31
  # If you pass additional options to the include directive, those are used
19
32
  # to initialize settings for the gem install process. For example:
20
33
  #
@@ -34,7 +47,8 @@ module Toys
34
47
  end
35
48
 
36
49
  ##
37
- # Activate the given gem.
50
+ # Activate the given gem. If it is not present, attempt to install it (or
51
+ # inform the user to update the bundle).
38
52
  #
39
53
  # @param name [String] Name of the gem
40
54
  # @param requirements [String...] Version requirements
@@ -49,84 +49,84 @@ module Toys
49
49
  # Calls [HighLine#agree](https://www.rubydoc.info/gems/highline/HighLine:agree)
50
50
  #
51
51
  def agree(*args, &block)
52
- highline.agree(*args, &block)
52
+ self[KEY].agree(*args, &block)
53
53
  end
54
54
 
55
55
  ##
56
56
  # Calls [HighLine#ask](https://www.rubydoc.info/gems/highline/HighLine:ask)
57
57
  #
58
58
  def ask(*args, &block)
59
- highline.ask(*args, &block)
59
+ self[KEY].ask(*args, &block)
60
60
  end
61
61
 
62
62
  ##
63
63
  # Calls [HighLine#choose](https://www.rubydoc.info/gems/highline/HighLine:choose)
64
64
  #
65
65
  def choose(*args, &block)
66
- highline.choose(*args, &block)
66
+ self[KEY].choose(*args, &block)
67
67
  end
68
68
 
69
69
  ##
70
70
  # Calls [HighLine#list](https://www.rubydoc.info/gems/highline/HighLine:list)
71
71
  #
72
72
  def list(*args, &block)
73
- highline.list(*args, &block)
73
+ self[KEY].list(*args, &block)
74
74
  end
75
75
 
76
76
  ##
77
77
  # Calls [HighLine#say](https://www.rubydoc.info/gems/highline/HighLine:say)
78
78
  #
79
79
  def say(*args, &block)
80
- highline.say(*args, &block)
80
+ self[KEY].say(*args, &block)
81
81
  end
82
82
 
83
83
  ##
84
84
  # Calls [HighLine#indent](https://www.rubydoc.info/gems/highline/HighLine:indent)
85
85
  #
86
86
  def indent(*args, &block)
87
- highline.indent(*args, &block)
87
+ self[KEY].indent(*args, &block)
88
88
  end
89
89
 
90
90
  ##
91
91
  # Calls [HighLine#newline](https://www.rubydoc.info/gems/highline/HighLine:newline)
92
92
  #
93
93
  def newline
94
- highline.newline
94
+ self[KEY].newline
95
95
  end
96
96
 
97
97
  ##
98
98
  # Calls [HighLine#puts](https://www.rubydoc.info/gems/highline/HighLine:puts)
99
99
  #
100
100
  def puts(*args)
101
- highline.puts(*args)
101
+ self[KEY].puts(*args)
102
102
  end
103
103
 
104
104
  ##
105
105
  # Calls [HighLine#color](https://www.rubydoc.info/gems/highline/HighLine:color)
106
106
  #
107
107
  def color(*args)
108
- highline.color(*args)
108
+ self[KEY].color(*args)
109
109
  end
110
110
 
111
111
  ##
112
112
  # Calls [HighLine#color_code](https://www.rubydoc.info/gems/highline/HighLine:color_code)
113
113
  #
114
114
  def color_code(*args)
115
- highline.color_code(*args)
115
+ self[KEY].color_code(*args)
116
116
  end
117
117
 
118
118
  ##
119
119
  # Calls [HighLine#uncolor](https://www.rubydoc.info/gems/highline/HighLine:uncolor)
120
120
  #
121
121
  def uncolor(*args)
122
- highline.uncolor(*args)
122
+ self[KEY].uncolor(*args)
123
123
  end
124
124
 
125
125
  ##
126
126
  # Calls [HighLine#new_scope](https://www.rubydoc.info/gems/highline/HighLine:new_scope)
127
127
  #
128
128
  def new_scope
129
- highline.new_scope
129
+ self[KEY].new_scope
130
130
  end
131
131
 
132
132
  on_initialize do |*args|
@@ -54,7 +54,7 @@ module Toys
54
54
  # @return [self]
55
55
  #
56
56
  def puts(str = "", *styles)
57
- terminal.puts(str, *styles)
57
+ self[KEY].puts(str, *styles)
58
58
  self
59
59
  end
60
60
  alias say puts
@@ -70,7 +70,7 @@ module Toys
70
70
  # @return [self]
71
71
  #
72
72
  def write(str = "", *styles)
73
- terminal.write(str, *styles)
73
+ self[KEY].write(str, *styles)
74
74
  self
75
75
  end
76
76
 
@@ -89,7 +89,7 @@ module Toys
89
89
  # @return [String]
90
90
  #
91
91
  def ask(prompt, *styles, default: nil, trailing_text: :default)
92
- terminal.ask(prompt, *styles, default: default, trailing_text: trailing_text)
92
+ self[KEY].ask(prompt, *styles, default: default, trailing_text: trailing_text)
93
93
  end
94
94
 
95
95
  ##
@@ -105,7 +105,7 @@ module Toys
105
105
  # @return [Boolean]
106
106
  #
107
107
  def confirm(prompt = "Proceed?", *styles, default: nil)
108
- terminal.confirm(prompt, *styles, default: default)
108
+ self[KEY].confirm(prompt, *styles, default: default)
109
109
  end
110
110
 
111
111
  ##
@@ -130,9 +130,9 @@ module Toys
130
130
  #
131
131
  def spinner(leading_text: "", final_text: "",
132
132
  frame_length: nil, frames: nil, style: nil, &block)
133
- terminal.spinner(leading_text: leading_text, final_text: final_text,
134
- frame_length: frame_length, frames: frames, style: style,
135
- &block)
133
+ self[KEY].spinner(leading_text: leading_text, final_text: final_text,
134
+ frame_length: frame_length, frames: frames, style: style,
135
+ &block)
136
136
  end
137
137
 
138
138
  on_initialize do |**opts|
@@ -18,12 +18,12 @@ module Toys
18
18
  ##
19
19
  # Create a completion given configuration options.
20
20
  #
21
- # @param complete_subtools [Boolean] Whether to complete subtool names
22
- # @param include_hidden_subtools [Boolean] Whether to include hidden
21
+ # @param complete_subtools [true,false] Whether to complete subtool names
22
+ # @param include_hidden_subtools [true,false] Whether to include hidden
23
23
  # 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
24
+ # @param complete_args [true,false] Whether to complete positional args
25
+ # @param complete_flags [true,false] Whether to complete flag names
26
+ # @param complete_flag_values [true,false] Whether to complete flag values
27
27
  # @param delegation_target [Array<String>,nil] Delegation target, or
28
28
  # `nil` if none.
29
29
  #
@@ -41,7 +41,7 @@ module Toys
41
41
 
42
42
  ##
43
43
  # Whether to complete subtool names
44
- # @return [Boolean]
44
+ # @return [true,false]
45
45
  #
46
46
  def complete_subtools?
47
47
  @complete_subtools
@@ -49,7 +49,7 @@ module Toys
49
49
 
50
50
  ##
51
51
  # Whether to include hidden subtools
52
- # @return [Boolean]
52
+ # @return [true,false]
53
53
  #
54
54
  def include_hidden_subtools?
55
55
  @include_hidden_subtools
@@ -57,7 +57,7 @@ module Toys
57
57
 
58
58
  ##
59
59
  # Whether to complete flags
60
- # @return [Boolean]
60
+ # @return [true,false]
61
61
  #
62
62
  def complete_flags?
63
63
  @complete_flags
@@ -65,7 +65,7 @@ module Toys
65
65
 
66
66
  ##
67
67
  # Whether to complete positional args
68
- # @return [Boolean]
68
+ # @return [true,false]
69
69
  #
70
70
  def complete_args?
71
71
  @complete_args
@@ -73,7 +73,7 @@ module Toys
73
73
 
74
74
  ##
75
75
  # Whether to complete flag values
76
- # @return [Boolean]
76
+ # @return [true,false]
77
77
  #
78
78
  def complete_flag_values?
79
79
  @complete_flag_values
@@ -205,7 +205,7 @@ module Toys
205
205
  #
206
206
  # The following settings are supported:
207
207
  #
208
- # * `propagate_helper_methods` (_Boolean_) - Whether subtools should
208
+ # * `propagate_helper_methods` (_boolean_) - Whether subtools should
209
209
  # inherit methods defined by parent tools. Defaults to `false`.
210
210
  #
211
211
  class Settings < ::Toys::Settings
@@ -273,7 +273,8 @@ module Toys
273
273
  @includes_modules = false
274
274
  @custom_context_directory = nil
275
275
 
276
- @interrupt_handler = nil
276
+ @run_handler = :run
277
+ @signal_handlers = {}
277
278
  @usage_error_handler = nil
278
279
  @delegate_target = nil
279
280
 
@@ -454,19 +455,33 @@ module Toys
454
455
  attr_reader :completion
455
456
 
456
457
  ##
457
- # The interrupt handler.
458
+ # The run handler.
458
459
  #
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
460
+ # This handler is called to run the tool. Normally it is a method name,
461
+ # represented by a symbol. (The default is `:run`.) It can be set to a
462
+ # different method name, or to a proc that will be called with `self` set
463
+ # to the tool context. Either way, it takes no arguments. The run handler
464
+ # can also be explicitly set to `nil` indicating a non-runnable tool;
465
+ # however, typically a tool is made non-runnable simply by leaving the run
466
+ # handler set to `:run` and not defining the method.
462
467
  #
463
- attr_reader :interrupt_handler
468
+ # @return [Proc] if the run handler is defined as a Proc
469
+ # @return [Symbol] if the run handler is defined as a method
470
+ # @return [nil] if the tool is explicitly made non-runnable
471
+ #
472
+ attr_reader :run_handler
464
473
 
465
474
  ##
466
475
  # The usage error handler.
467
476
  #
468
- # @return [Proc] The usage error handler proc
469
- # @return [Symbol] The name of a method to call
477
+ # This handler is called when at least one usage error is detected during
478
+ # argument parsing, and is called instead of the `run` method. It can be
479
+ # specified as a Proc, or a Symbol indicating a method to call. It
480
+ # optionally takes an array of {Toys::ArgParser::UsageError} as the sole
481
+ # argument.
482
+ #
483
+ # @return [Proc] if the usage error handler is defined as a Proc
484
+ # @return [Symbol] if the user error handler is defined as a method
470
485
  # @return [nil] if there is no usage error handler
471
486
  #
472
487
  attr_reader :usage_error_handler
@@ -498,9 +513,37 @@ module Toys
498
513
  full_name.join(" ")
499
514
  end
500
515
 
516
+ ##
517
+ # Return the signal handler for the given signal.
518
+ #
519
+ # This handler is called when the given signal is received, immediately
520
+ # taking over the execution as if it were the new run handler. The signal
521
+ # handler can be specified as a Proc, or a Symbol indicating a method to
522
+ # call. It optionally takes the `SignalException` as the sole argument.
523
+ #
524
+ # @param signal [Integer,String,Symbol] The signal number or name
525
+ # @return [Proc] if the signal handler is defined as a Proc
526
+ # @return [Symbol] if the signal handler is defined as a method
527
+ # @return [nil] if there is no handler for the given signal
528
+ #
529
+ def signal_handler(signal)
530
+ @signal_handlers[canonicalize_signal(signal)]
531
+ end
532
+
533
+ ##
534
+ # Return the interrupt handler. This is equivalent to `signal_handler(2)`.
535
+ #
536
+ # @return [Proc] if the interrupt signal handler is defined as a Proc
537
+ # @return [Symbol] if the interrupt signal handler is defined as a method
538
+ # @return [nil] if there is no handler for the interrupt signals
539
+ #
540
+ def interrupt_handler
541
+ signal_handler(2)
542
+ end
543
+
501
544
  ##
502
545
  # Returns true if this tool is a root tool.
503
- # @return [Boolean]
546
+ # @return [true,false]
504
547
  #
505
548
  def root?
506
549
  full_name.empty?
@@ -508,23 +551,38 @@ module Toys
508
551
 
509
552
  ##
510
553
  # Returns true if this tool is marked as runnable.
511
- # @return [Boolean]
554
+ # @return [true,false]
512
555
  #
513
556
  def runnable?
514
- tool_class.public_instance_methods(false).include?(:run)
557
+ @run_handler.is_a?(::Symbol) &&
558
+ tool_class.public_instance_methods(false).include?(@run_handler) ||
559
+ @run_handler.is_a?(::Proc)
515
560
  end
516
561
 
517
562
  ##
518
- # Returns true if this tool handles interrupts.
519
- # @return [Boolean]
563
+ # Returns true if this tool handles interrupts. This is equivalent to
564
+ # `handles_signal?(2)`.
565
+ #
566
+ # @return [true,false]
520
567
  #
521
568
  def handles_interrupts?
522
- !interrupt_handler.nil?
569
+ handles_signal?(2)
570
+ end
571
+
572
+ ##
573
+ # Returns true if this tool handles the given signal.
574
+ #
575
+ # @param signal [Integer,String,Symbol] The signal number or name
576
+ # @return [true,false]
577
+ #
578
+ def handles_signal?(signal)
579
+ signal = canonicalize_signal(signal)
580
+ !@signal_handlers[signal].nil?
523
581
  end
524
582
 
525
583
  ##
526
584
  # Returns true if this tool handles usage errors.
527
- # @return [Boolean]
585
+ # @return [true,false]
528
586
  #
529
587
  def handles_usage_errors?
530
588
  !usage_error_handler.nil?
@@ -532,7 +590,7 @@ module Toys
532
590
 
533
591
  ##
534
592
  # Returns true if this tool has at least one included module.
535
- # @return [Boolean]
593
+ # @return [true,false]
536
594
  #
537
595
  def includes_modules?
538
596
  @includes_modules
@@ -540,7 +598,7 @@ module Toys
540
598
 
541
599
  ##
542
600
  # Returns true if there is a specific description set for this tool.
543
- # @return [Boolean]
601
+ # @return [true,false]
544
602
  #
545
603
  def includes_description?
546
604
  !long_desc.empty? || !desc.empty?
@@ -549,7 +607,7 @@ module Toys
549
607
  ##
550
608
  # Returns true if at least one flag or positional argument is defined
551
609
  # for this tool.
552
- # @return [Boolean]
610
+ # @return [true,false]
553
611
  #
554
612
  def includes_arguments?
555
613
  !default_data.empty? || !flags.empty? ||
@@ -559,7 +617,7 @@ module Toys
559
617
 
560
618
  ##
561
619
  # Returns true if this tool has any definition information.
562
- # @return [Boolean]
620
+ # @return [true,false]
563
621
  #
564
622
  def includes_definition?
565
623
  includes_arguments? || runnable? || argument_parsing_disabled? ||
@@ -568,7 +626,7 @@ module Toys
568
626
 
569
627
  ##
570
628
  # Returns true if this tool's definition has been finished and is locked.
571
- # @return [Boolean]
629
+ # @return [true,false]
572
630
  #
573
631
  def definition_finished?
574
632
  @definition_finished
@@ -576,7 +634,7 @@ module Toys
576
634
 
577
635
  ##
578
636
  # Returns true if this tool has disabled argument parsing.
579
- # @return [Boolean]
637
+ # @return [true,false]
580
638
  #
581
639
  def argument_parsing_disabled?
582
640
  @disable_argument_parsing
@@ -584,7 +642,7 @@ module Toys
584
642
 
585
643
  ##
586
644
  # Returns true if this tool enforces flags before args.
587
- # @return [Boolean]
645
+ # @return [true,false]
588
646
  #
589
647
  def flags_before_args_enforced?
590
648
  @enforce_flags_before_args
@@ -592,7 +650,7 @@ module Toys
592
650
 
593
651
  ##
594
652
  # Returns true if this tool requires exact flag matches.
595
- # @return [Boolean]
653
+ # @return [true,false]
596
654
  #
597
655
  def exact_flag_match_required?
598
656
  @require_exact_flag_match
@@ -867,7 +925,7 @@ module Toys
867
925
  # Enforce that flags must come before args for this tool.
868
926
  # You may disable enforcement by passoing `false` for the state.
869
927
  #
870
- # @param state [Boolean]
928
+ # @param state [true,false]
871
929
  # @return [self]
872
930
  #
873
931
  def enforce_flags_before_args(state = true)
@@ -885,7 +943,7 @@ module Toys
885
943
  # Require that flags must match exactly. (If false, flags can match an
886
944
  # unambiguous substring.)
887
945
  #
888
- # @param state [Boolean]
946
+ # @param state [true,false]
889
947
  # @return [self]
890
948
  #
891
949
  def require_exact_flag_match(state = true)
@@ -919,10 +977,10 @@ module Toys
919
977
  # formats. Defaults to the empty array.
920
978
  # @param name [String,Symbol,nil] The name of the group, or nil for no
921
979
  # name.
922
- # @param report_collisions [Boolean] If `true`, raise an exception if a
980
+ # @param report_collisions [true,false] If `true`, raise an exception if a
923
981
  # the given name is already taken. If `false`, ignore. Default is
924
982
  # `true`.
925
- # @param prepend [Boolean] If `true`, prepend rather than append the
983
+ # @param prepend [true,false] If `true`, prepend rather than append the
926
984
  # group to the list. Default is `false`.
927
985
  # @return [self]
928
986
  #
@@ -977,7 +1035,7 @@ module Toys
977
1035
  # @param complete_values [Object] A specifier for shell tab completion
978
1036
  # for flag values associated with this flag. Pass any spec
979
1037
  # recognized by {Toys::Completion.create}.
980
- # @param report_collisions [Boolean] Raise an exception if a flag is
1038
+ # @param report_collisions [true,false] Raise an exception if a flag is
981
1039
  # requested that is already in use or marked as disabled. Default is
982
1040
  # true.
983
1041
  # @param group [Toys::FlagGroup,String,Symbol,nil] Group for
@@ -1147,33 +1205,65 @@ module Toys
1147
1205
  end
1148
1206
 
1149
1207
  ##
1150
- # Set the run handler block
1208
+ # Set the run handler.
1209
+ #
1210
+ # This handler is called to run the tool. Normally it is a method name,
1211
+ # represented by a symbol. (The default is `:run`.) It can be set to a
1212
+ # different method name, or to a proc that will be called with `self` set
1213
+ # to the tool context. Either way, it takes no arguments. The run handler
1214
+ # can also be explicitly set to `nil` indicating a non-runnable tool;
1215
+ # however, typically a tool is made non-runnable simply by leaving the run
1216
+ # handler set to `:run` and not defining the method.
1151
1217
  #
1152
- # @param proc [Proc] The runnable block
1218
+ # @param handler [Proc,Symbol,nil] the run handler
1153
1219
  #
1154
- def run_handler=(proc)
1220
+ def run_handler=(handler)
1155
1221
  check_definition_state(is_method: true)
1156
- tool_class.class_eval do
1157
- define_method(:run, &proc)
1222
+ if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
1223
+ raise ToolDefinitionError, "Run handler must be a proc or symbol"
1158
1224
  end
1225
+ @run_handler = handler
1159
1226
  end
1160
1227
 
1161
1228
  ##
1162
- # Set the interrupt handler.
1229
+ # Set the interrupt handler. This is equivalent to calling
1230
+ # {#set_signal_handler} for the `SIGINT` signal.
1163
1231
  #
1164
- # @param handler [Proc,Symbol] The interrupt handler
1232
+ # @param handler [Proc,Symbol] The interrupt signal handler
1165
1233
  #
1166
1234
  def interrupt_handler=(handler)
1235
+ set_signal_handler(2, handler)
1236
+ end
1237
+
1238
+ ##
1239
+ # Set the handler for the given signal.
1240
+ #
1241
+ # This handler is called when the given signal is received, immediately
1242
+ # taking over the execution as if it were the new `run` method. The signal
1243
+ # handler can be specified as a Proc, or a Symbol indicating a method to
1244
+ # call. It optionally takes the `SignalException` as the sole argument.
1245
+ #
1246
+ # @param signal [Integer,String,Symbol] The signal number or name
1247
+ # @param handler [Proc,Symbol] The signal handler
1248
+ #
1249
+ def set_signal_handler(signal, handler)
1167
1250
  check_definition_state(is_method: true)
1168
1251
  if !handler.is_a?(::Proc) && !handler.is_a?(::Symbol) && !handler.nil?
1169
- raise ToolDefinitionError, "Interrupt handler must be a proc or symbol"
1252
+ raise ToolDefinitionError, "Signal handler must be a proc or symbol"
1170
1253
  end
1171
- @interrupt_handler = handler
1254
+ signal = canonicalize_signal(signal)
1255
+ @signal_handlers[signal] = handler
1172
1256
  end
1173
1257
 
1174
1258
  ##
1175
1259
  # Set the usage error handler.
1176
1260
  #
1261
+ # This handler is called when at least one usage error is detected during
1262
+ # argument parsing, and is called instead of the `run` method. It can be
1263
+ # specified as a Proc, or a Symbol indicating a method to call. It
1264
+ # optionally takes an array of {Toys::ArgParser::UsageError} as the sole
1265
+ # argument.
1266
+ #
1177
1267
  # @param handler [Proc,Symbol] The usage error handler
1178
1268
  #
1179
1269
  def usage_error_handler=(handler)
@@ -1374,14 +1464,14 @@ module Toys
1374
1464
  name = walk_context[::Toys::Context::Key::TOOL_NAME]
1375
1465
  path << name.join(" ").inspect
1376
1466
  if name == target
1377
- raise "Delegation loop: #{path.join(' <- ')}"
1467
+ raise ToolDefinitionError, "Delegation loop: #{path.join(' <- ')}"
1378
1468
  end
1379
1469
  walk_context = walk_context[::Toys::Context::Key::DELEGATED_FROM]
1380
1470
  end
1381
1471
  cli = self[::Toys::Context::Key::CLI]
1382
1472
  cli.loader.load_for_prefix(target)
1383
1473
  unless cli.loader.tool_defined?(target)
1384
- raise "Delegate target not found: \"#{target.join(' ')}\""
1474
+ raise ToolDefinitionError, "Delegate target not found: \"#{target.join(' ')}\""
1385
1475
  end
1386
1476
  exit(cli.run(target + self[::Toys::Context::Key::ARGS], delegated_from: self))
1387
1477
  end
@@ -1400,5 +1490,18 @@ module Toys
1400
1490
  raise ToolDefinitionError, "Unknown completion: #{name.inspect}" if completion.nil?
1401
1491
  completion
1402
1492
  end
1493
+
1494
+ def canonicalize_signal(signal)
1495
+ case signal
1496
+ when ::String, ::Symbol
1497
+ sigstr = signal.to_s
1498
+ sigstr = sigstr[3..-1] if sigstr.start_with?("SIG")
1499
+ signo = ::Signal.list[sigstr]
1500
+ return signo if signo
1501
+ when ::Integer
1502
+ return signal if ::Signal.signame(signal)
1503
+ end
1504
+ raise ::ArgumentError, "Unknown signal: #{signal}"
1505
+ end
1403
1506
  end
1404
1507
  end
@@ -16,6 +16,27 @@ module Toys
16
16
  # This class is not loaded by default. Before using it directly, you should
17
17
  # `require "toys/utils/exec"`
18
18
  #
19
+ # ### The exec service
20
+ #
21
+ # The main entrypoint class is this one, {Toys::Utils::Exec}. It's a
22
+ # "service" object that provides functionality, primarily methods that
23
+ # spawn processes. Create it like any object:
24
+ #
25
+ # require "toys/utils/exec"
26
+ # exec_service = Toys::Utils::Exec.new
27
+ #
28
+ # There are two "primitive" functions: {#exec} and {#exec_proc}. The {#exec}
29
+ # method spawns an operating system process specified by an executable and
30
+ # a set of arguments. The {#exec_proc} method takes a `Proc` and forks a
31
+ # Ruby process. Both of these can be heavily configured with stream
32
+ # handling, result handling, and numerous other options described below.
33
+ # The class also provides convenience methods for common cases such as
34
+ # spawning a Ruby process, spawning a shell script, or capturing output.
35
+ #
36
+ # The exec service class also stores default configuration that it applies
37
+ # to processes it spawns. You can set these defaults when constructing the
38
+ # service class, or at any time by calling {#configure_defaults}.
39
+ #
19
40
  # ### Controlling processes
20
41
  #
21
42
  # A process can be started in the *foreground* or the *background*. If you
@@ -24,7 +45,7 @@ module Toys
24
45
  # If you start a background process, its streams will be redirected to null
25
46
  # by default, and control will be returned to you immediately.
26
47
  #
27
- # When a process is running, you can control it using a
48
+ # While a process is running, you can control it using a
28
49
  # {Toys::Utils::Exec::Controller} object. Use a controller to interact with
29
50
  # the process's input and output streams, send it signals, or wait for it
30
51
  # to complete.
@@ -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)