toys-core 0.9.4 → 0.10.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -1
  3. data/CHANGELOG.md +30 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +3 -3
  6. data/lib/toys-core.rb +11 -21
  7. data/lib/toys/acceptor.rb +0 -21
  8. data/lib/toys/arg_parser.rb +1 -22
  9. data/lib/toys/cli.rb +102 -70
  10. data/lib/toys/compat.rb +49 -41
  11. data/lib/toys/completion.rb +0 -21
  12. data/lib/toys/context.rb +0 -23
  13. data/lib/toys/core.rb +1 -22
  14. data/lib/toys/dsl/flag.rb +0 -21
  15. data/lib/toys/dsl/flag_group.rb +0 -21
  16. data/lib/toys/dsl/positional_arg.rb +0 -21
  17. data/lib/toys/dsl/tool.rb +135 -51
  18. data/lib/toys/errors.rb +0 -21
  19. data/lib/toys/flag.rb +0 -21
  20. data/lib/toys/flag_group.rb +0 -21
  21. data/lib/toys/input_file.rb +0 -21
  22. data/lib/toys/loader.rb +41 -78
  23. data/lib/toys/middleware.rb +146 -77
  24. data/lib/toys/mixin.rb +0 -21
  25. data/lib/toys/module_lookup.rb +3 -26
  26. data/lib/toys/positional_arg.rb +0 -21
  27. data/lib/toys/source_info.rb +49 -38
  28. data/lib/toys/standard_middleware/add_verbosity_flags.rb +0 -23
  29. data/lib/toys/standard_middleware/apply_config.rb +42 -0
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +7 -28
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +0 -23
  32. data/lib/toys/standard_middleware/show_help.rb +0 -23
  33. data/lib/toys/standard_middleware/show_root_version.rb +0 -23
  34. data/lib/toys/standard_mixins/bundler.rb +89 -0
  35. data/lib/toys/standard_mixins/exec.rb +124 -35
  36. data/lib/toys/standard_mixins/fileutils.rb +0 -21
  37. data/lib/toys/standard_mixins/gems.rb +2 -24
  38. data/lib/toys/standard_mixins/highline.rb +0 -21
  39. data/lib/toys/standard_mixins/terminal.rb +0 -21
  40. data/lib/toys/template.rb +0 -21
  41. data/lib/toys/tool.rb +22 -34
  42. data/lib/toys/utils/completion_engine.rb +0 -21
  43. data/lib/toys/utils/exec.rb +1 -21
  44. data/lib/toys/utils/gems.rb +174 -63
  45. data/lib/toys/utils/help_text.rb +0 -21
  46. data/lib/toys/utils/terminal.rb +46 -37
  47. data/lib/toys/wrappable_string.rb +0 -21
  48. metadata +25 -9
@@ -1,26 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
23
-
24
3
  module Toys
25
4
  module StandardMixins
26
5
  ##
@@ -1,26 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
23
-
24
3
  module Toys
25
4
  ##
26
5
  # A template definition. Template classes should include this module.
@@ -1,26 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
23
-
24
3
  require "set"
25
4
 
26
5
  module Toys
@@ -37,11 +16,12 @@ module Toys
37
16
  # Should be created only from the DSL via the Loader.
38
17
  # @private
39
18
  #
40
- def initialize(loader, parent, full_name, priority, middleware_stack)
19
+ def initialize(loader, parent, full_name, priority, middleware_stack, middleware_lookup)
41
20
  @parent = parent
42
21
  @full_name = full_name.dup.freeze
43
22
  @priority = priority
44
- @middleware_stack = middleware_stack
23
+ @built_middleware = middleware_stack.build(middleware_lookup)
24
+ @subtool_middleware_stack = middleware_stack.dup
45
25
 
46
26
  @acceptors = {}
47
27
  @mixins = {}
@@ -201,11 +181,20 @@ module Toys
201
181
  attr_reader :default_data
202
182
 
203
183
  ##
204
- # The middleware stack active for this tool.
184
+ # The stack of middleware specs used for subtools.
185
+ #
186
+ # This array may be modified in place.
187
+ #
188
+ # @return [Array<Toys::Middleware::Spec>]
189
+ #
190
+ attr_reader :subtool_middleware_stack
191
+
192
+ ##
193
+ # The stack of built middleware specs for this tool.
205
194
  #
206
195
  # @return [Array<Toys::Middleware>]
207
196
  #
208
- attr_reader :middleware_stack
197
+ attr_reader :built_middleware
209
198
 
210
199
  ##
211
200
  # Info on the source of this tool.
@@ -473,18 +462,13 @@ module Toys
473
462
  ##
474
463
  # Sets the path to the file that defines this tool.
475
464
  # A tool may be defined from at most one path. If a different path is
476
- # already set, raises {Toys::ToolDefinitionError}
465
+ # already set, it is left unchanged.
477
466
  #
478
467
  # @param source [Toys::SourceInfo] Source info
479
468
  # @return [self]
480
469
  #
481
470
  def lock_source(source)
482
- if source_info && source_info.source != source.source
483
- raise ToolDefinitionError,
484
- "Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
485
- " (already defined in #{source_info.source_name})"
486
- end
487
- @source_info = source
471
+ @source_info ||= source
488
472
  self
489
473
  end
490
474
 
@@ -1073,7 +1057,7 @@ module Toys
1073
1057
  unless @definition_finished
1074
1058
  ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
1075
1059
  config_proc = proc {}
1076
- middleware_stack.reverse_each do |middleware|
1060
+ @built_middleware.reverse_each do |middleware|
1077
1061
  config_proc = make_config_proc(middleware, loader, config_proc)
1078
1062
  end
1079
1063
  config_proc.call
@@ -1306,7 +1290,11 @@ module Toys
1306
1290
  private
1307
1291
 
1308
1292
  def make_config_proc(middleware, loader, next_config)
1309
- proc { middleware.config(self, loader, &next_config) }
1293
+ if middleware.respond_to?(:config)
1294
+ proc { middleware.config(self, loader, &next_config) }
1295
+ else
1296
+ next_config
1297
+ end
1310
1298
  end
1311
1299
 
1312
1300
  def make_delegation_run_handler(target)
@@ -1,26 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
23
-
24
3
  require "shellwords"
25
4
 
26
5
  module Toys
@@ -1,26 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
23
-
3
+ require "rbconfig"
24
4
  require "logger"
25
5
  require "shellwords"
26
6
 
@@ -1,30 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
- ;
3
+ require "monitor"
4
+ require "rubygems"
23
5
 
24
6
  module Toys
25
7
  module Utils
26
8
  ##
27
- # A helper module that activates and installs gems.
9
+ # A helper class that activates and installs gems and sets up bundler.
28
10
  #
29
11
  # This class is not loaded by default. Before using it directly, you should
30
12
  # `require "toys/utils/gems"`
@@ -58,6 +40,30 @@ module Toys
58
40
  end
59
41
  end
60
42
 
43
+ ##
44
+ # Failed to run Bundler
45
+ #
46
+ class BundlerFailedError < ::StandardError
47
+ end
48
+
49
+ ##
50
+ # Could not find a Gemfile
51
+ #
52
+ class GemfileNotFoundError < BundlerFailedError
53
+ end
54
+
55
+ ##
56
+ # The bundle is not and could not be installed
57
+ #
58
+ class BundleNotInstalledError < BundlerFailedError
59
+ end
60
+
61
+ ##
62
+ # Bundler has already been run; cannot do so again
63
+ #
64
+ class AlreadyBundledError < BundlerFailedError
65
+ end
66
+
61
67
  ##
62
68
  # Activate the given gem. If it is not present, attempt to install it (or
63
69
  # inform the user to update the bundle).
@@ -73,24 +79,42 @@ module Toys
73
79
  ##
74
80
  # Create a new gem activator.
75
81
  #
76
- # @param input [IO] Input IO
77
- # @param output [IO] Output IO
78
- # @param suppress_confirm [Boolean] Suppress the confirmation prompt and
79
- # just use the given `default_confirm` value. Default is false,
80
- # indicating the confirmation prompt appears by default.
81
- # @param default_confirm [Boolean] Default response for the confirmation
82
- # prompt. Default is true.
82
+ # @param on_missing [:confirm,:error,:install] What to do if a needed gem
83
+ # is not installed. Possible values:
84
+ # * `:confirm` - prompt the user on whether to install
85
+ # * `:error` - raise an exception
86
+ # * `:install` - just install the gem
87
+ # The default is `:confirm`.
88
+ # @param on_conflict [:error,:warn,:ignore] What to do if bundler has
89
+ # already been run with a different Gemfile. Possible values:
90
+ # * `:error` - raise an exception
91
+ # * `:ignore` - just silently proceed without bundling again
92
+ # * `:warn` - print a warning and proceed without bundling again
93
+ # The default is `:error`.
94
+ # @param terminal [Toys::Utils::Terminal] Terminal to use (optional)
95
+ # @param input [IO] Input IO (optional, defaults to STDIN)
96
+ # @param output [IO] Output IO (optional, defaults to STDOUT)
97
+ # @param suppress_confirm [Boolean] Deprecated. Use `on_missing` instead.
98
+ # @param default_confirm [Boolean] Deprecated. Use `on_missing` instead.
83
99
  #
84
- def initialize(input: $stdin,
85
- output: $stderr,
86
- suppress_confirm: false,
87
- default_confirm: true)
88
- require "toys/utils/terminal"
89
- require "toys/utils/exec"
90
- @terminal = Utils::Terminal.new(input: input, output: output)
91
- @exec = Utils::Exec.new
92
- @suppress_confirm = suppress_confirm ? true : false
93
- @default_confirm = default_confirm ? true : false
100
+ def initialize(on_missing: nil,
101
+ on_conflict: nil,
102
+ terminal: nil,
103
+ input: nil,
104
+ output: nil,
105
+ suppress_confirm: nil,
106
+ default_confirm: nil)
107
+ @default_confirm = default_confirm || default_confirm.nil? ? true : false
108
+ @on_missing = on_missing ||
109
+ if suppress_confirm
110
+ @default_confirm ? :install : :error
111
+ else
112
+ :confirm
113
+ end
114
+ @on_conflict = on_conflict || :error
115
+ @terminal = terminal
116
+ @input = input || ::STDIN
117
+ @output = output || ::STDOUT
94
118
  end
95
119
 
96
120
  ##
@@ -102,13 +126,56 @@ module Toys
102
126
  # @return [void]
103
127
  #
104
128
  def activate(name, *requirements)
105
- gem(name, *requirements)
106
- rescue ::Gem::LoadError => e
107
- handle_activation_error(e, name, requirements)
129
+ Gems.synchronize do
130
+ begin
131
+ gem(name, *requirements)
132
+ rescue ::Gem::LoadError => e
133
+ handle_activation_error(e, name, requirements)
134
+ end
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Set up the bundle.
140
+ #
141
+ # @param groups [Array<String>] The groups to include in setup
142
+ # @param search_dirs [Array<String>] Directories to search for a Gemfile
143
+ # @return [void]
144
+ #
145
+ def bundle(groups: nil,
146
+ search_dirs: nil)
147
+ Gems.synchronize do
148
+ gemfile_path = find_gemfile(Array(search_dirs))
149
+ activate("bundler", "~> 2.1")
150
+ if configure_gemfile(gemfile_path)
151
+ setup_bundle(gemfile_path, groups || [])
152
+ end
153
+ end
154
+ end
155
+
156
+ @global_mutex = ::Monitor.new
157
+
158
+ ## @private
159
+ def self.synchronize(&block)
160
+ @global_mutex.synchronize(&block)
108
161
  end
109
162
 
110
163
  private
111
164
 
165
+ def terminal
166
+ @terminal ||= begin
167
+ require "toys/utils/terminal"
168
+ Utils::Terminal.new(input: @input, output: @output)
169
+ end
170
+ end
171
+
172
+ def exec_util
173
+ @exec_util ||= begin
174
+ require "toys/utils/exec"
175
+ Utils::Exec.new
176
+ end
177
+ end
178
+
112
179
  def handle_activation_error(error, name, requirements)
113
180
  is_missing_spec =
114
181
  if defined?(::Gem::MissingSpecError)
@@ -116,11 +183,11 @@ module Toys
116
183
  else
117
184
  error.message.include?("Could not find")
118
185
  end
119
- unless is_missing_spec
186
+ if !is_missing_spec || @on_missing == :error
120
187
  report_error(name, requirements, error)
121
188
  return
122
189
  end
123
- install_gem(name, requirements)
190
+ confirm_and_install_gem(name, requirements)
124
191
  begin
125
192
  gem(name, *requirements)
126
193
  rescue ::Gem::LoadError => e
@@ -132,28 +199,16 @@ module Toys
132
199
  "#{name.inspect}, #{requirements.map(&:inspect).join(', ')}"
133
200
  end
134
201
 
135
- def install_gem(name, requirements)
136
- requirements_text = gem_requirements_text(name, requirements)
137
- response =
138
- if @suppress_confirm
139
- @default_confirm
140
- else
141
- @terminal.confirm("Gem needed: #{requirements_text}. Install? ",
142
- default: @default_confirm)
202
+ def confirm_and_install_gem(name, requirements)
203
+ if @on_missing == :confirm
204
+ requirements_text = gem_requirements_text(name, requirements)
205
+ response = terminal.confirm("Gem needed: #{requirements_text}. Install? ",
206
+ default: @default_confirm)
207
+ unless response
208
+ raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
143
209
  end
144
- unless response
145
- raise InstallFailedError, "Canceled installation of needed gem: #{requirements_text}"
146
- end
147
- perform_install(name, requirements)
148
- end
149
-
150
- def perform_install(name, requirements)
151
- result = @terminal.spinner(leading_text: "Installing gem #{name}... ",
152
- final_text: "Done.\n") do
153
- @exec.exec(["gem", "install", name, "--version", requirements.join(",")],
154
- out: :capture, err: :capture)
155
210
  end
156
- @terminal.puts(result.captured_out + result.captured_err)
211
+ result = exec_util.exec(["gem", "install", name, "--version", requirements.join(",")])
157
212
  if result.error?
158
213
  raise InstallFailedError, "Failed to install gem #{name}"
159
214
  end
@@ -167,6 +222,62 @@ module Toys
167
222
  end
168
223
  raise ActivationFailedError, err.message
169
224
  end
225
+
226
+ def find_gemfile(search_dirs)
227
+ search_dirs.each do |dir|
228
+ gemfile_path = ::File.join(dir, "Gemfile")
229
+ return gemfile_path if ::File.readable?(gemfile_path)
230
+ end
231
+ raise GemfileNotFoundError, "Gemfile not found"
232
+ end
233
+
234
+ def configure_gemfile(gemfile_path)
235
+ old_path = ::ENV["BUNDLE_GEMFILE"]
236
+ if old_path && gemfile_path != old_path
237
+ case @on_conflict
238
+ when :warn
239
+ terminal.puts("Warning: could not set up bundler because it is already set up.", :red)
240
+ when :error
241
+ raise AlreadyBundledError, "Could not set up bundler because it is already set up"
242
+ end
243
+ return false
244
+ end
245
+ ::ENV["BUNDLE_GEMFILE"] = gemfile_path
246
+ true
247
+ end
248
+
249
+ def setup_bundle(gemfile_path, groups)
250
+ require "bundler"
251
+ begin
252
+ ::Bundler.setup(*groups)
253
+ rescue ::Bundler::GemNotFound
254
+ install_bundle(gemfile_path)
255
+ ::Bundler.reset!
256
+ ::Bundler.setup(*groups)
257
+ end
258
+ end
259
+
260
+ def permission_to_bundle?
261
+ case @on_missing
262
+ when :install
263
+ true
264
+ when :error
265
+ false
266
+ else
267
+ terminal.confirm("Your bundle is not complete. Install? ", default: @default_confirm)
268
+ end
269
+ end
270
+
271
+ def install_bundle(gemfile_path)
272
+ gemfile_dir = ::File.dirname(gemfile_path)
273
+ unless permission_to_bundle?
274
+ raise BundleNotInstalledError,
275
+ "Your bundle is not installed. Consider running" \
276
+ " `cd #{gemfile_dir} && bundle install`"
277
+ end
278
+ require "bundler/cli"
279
+ ::Bundler::CLI.start(["install"])
280
+ end
170
281
  end
171
282
  end
172
283
  end