toys-core 0.14.6 → 0.15.0

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