backup 3.2.0 → 3.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 97b183cb0d0c210153909788775e95a6a686a4cc
4
- data.tar.gz: 4eb3b105dcdea08f667c8e94625ec9bdf174dc49
3
+ metadata.gz: 8030ea8ce771bafd23266be65ddac30e62d9954b
4
+ data.tar.gz: bc53925b9956147157e9200d4cfb9d8c7886d343
5
5
  SHA512:
6
- metadata.gz: d5c1cd997c5e0823022b323b639c7977b7d3f573468c860f0fab496ed619b3b44baae8c56ab8dbe13e9ab0643844dac72be9ac6e79c19ef5859faf2fd05760c5
7
- data.tar.gz: 07af01b103adf78549d1296145403dded6e8e6d4cea9dbd5b750839ca26f585c32e0c42a0359f46f9ac0bcc57a412e6b6fdb3e8ab5b8b0936f8496109af7ce09
6
+ metadata.gz: b9631215669b56c92b1694d5a1005d5292ae9d3e25dd2da717cfbe40e30af80d6163b38e6662b7c309c2b480d4d69fe953458171c7dcd5fa908a51314d1ab584
7
+ data.tar.gz: 799f65d322c49d396d9301163412db25369ced068c6260a66fb54444cbd234da73fa07d92c3f6b8e8a58f6bfc241d763c1261d475dc2d3580f6acee02e295cce
data/README.md CHANGED
@@ -68,10 +68,8 @@ $ tar -tvf my_backup.tar
68
68
  my_backup/archives/user_avatars.tar.gz
69
69
  my_backup/archives/log_files.tar.gz
70
70
  my_backup/databases/
71
- my_backup/databases/PostgreSQL/
72
- my_backup/databases/PostgreSQL/pg_db_name.sql.gz
73
- my_backup/databases/Redis/
74
- my_backup/databases/Redis/redis_db_name.rdb.gz
71
+ my_backup/databases/PostgreSQL.sql.gz
72
+ my_backup/databases/Redis.rdb.gz
75
73
  ```
76
74
 
77
75
  ### Storages
@@ -200,7 +198,7 @@ Backup::Model.new(:my_backup, 'Description for my_backup') do
200
198
  mail.user_name = "sender@email.com"
201
199
  mail.password = "my_password"
202
200
  mail.authentication = "plain"
203
- mail.enable_starttls_auto = true
201
+ mail.encryption = :starttls
204
202
  end
205
203
 
206
204
  notify_by Twitter do |tweet|
@@ -53,6 +53,7 @@ module Backup
53
53
  @model = model
54
54
  @name = name.to_s
55
55
  @options = {
56
+ :sudo => false,
56
57
  :root => false,
57
58
  :paths => [],
58
59
  :excludes => [],
@@ -69,7 +70,7 @@ module Backup
69
70
 
70
71
  pipeline = Pipeline.new
71
72
  pipeline.add(
72
- "#{ utility(:tar) } #{ tar_options } -cPf -#{ tar_root } " +
73
+ "#{ tar_command } #{ tar_options } -cPf -#{ tar_root } " +
73
74
  "#{ paths_to_exclude } #{ paths_to_package }",
74
75
  tar_success_codes
75
76
  )
@@ -95,6 +96,11 @@ module Backup
95
96
 
96
97
  private
97
98
 
99
+ def tar_command
100
+ tar = utility(:tar)
101
+ options[:sudo] ? "#{ utility(:sudo) } -n #{ tar }" : tar
102
+ end
103
+
98
104
  def tar_root
99
105
  options[:root] ? " -C '#{ File.expand_path(options[:root]) }'" : ''
100
106
  end
@@ -129,6 +135,10 @@ module Backup
129
135
  @options = options
130
136
  end
131
137
 
138
+ def use_sudo(val = true)
139
+ @options[:sudo] = val
140
+ end
141
+
132
142
  def root(path)
133
143
  @options[:root] = path
134
144
  end
data/lib/backup/cli.rb CHANGED
@@ -4,25 +4,17 @@
4
4
  # Build the Backup Command Line Interface using Thor
5
5
  module Backup
6
6
  class CLI < Thor
7
- include Thor::Actions
8
7
 
9
8
  ##
10
9
  # [Perform]
11
- # Performs the backup process. The only required option is the --trigger [-t].
12
- # If the other options (--config-file, --data-path, --cache-path, --tmp-path)
13
- # aren't specified they will fallback to the (good) defaults.
14
10
  #
11
+ # The only required option is the --trigger [-t].
12
+ # If --config-file, --data-path, --cache-path, --tmp-path aren't specified
13
+ # they will fallback to defaults defined in Backup::Config.
15
14
  # If --root-path is given, it will be used as the base path for our defaults,
16
15
  # as well as the base path for any option specified as a relative path.
17
16
  # Any option given as an absolute path will be used "as-is".
18
17
  #
19
- # If the --check option is given, the config.rb and all model files will be
20
- # loaded, but no triggers will be run. If the check fails, errors will be
21
- # reported to the console. If the check passes, a success message will be
22
- # reported to the console unless --quiet is set. Use --no-quiet to ensure
23
- # these messages are output. The command will exit with status 0 if
24
- # successful, or status 1 if there were problems.
25
- #
26
18
  # This command will exit with one of the following status codes:
27
19
  #
28
20
  # 0: All triggers were successful and no warnings were issued.
@@ -30,128 +22,201 @@ module Backup
30
22
  # 2: All triggers were processed, but some failed.
31
23
  # 3: A fatal error caused Backup to exit.
32
24
  # Some triggers may not have been processed.
25
+ #
26
+ # If the --check option is given, `backup check` will be run
27
+ # and no triggers will be performed.
33
28
  desc 'perform', "Performs the backup for the specified trigger(s)."
34
- long_desc "Performs the backup for the specified trigger(s).\n\n" +
35
- "You may perform multiple backups by providing multiple triggers, separated by commas.\n\n" +
36
- "Example:\n\s\s$ backup perform --triggers backup1,backup2,backup3,backup4\n\n" +
37
- "This will invoke 4 backups, and they will run in the order specified (not asynchronous).\n\n" +
38
- "\n\n" +
39
- "--root-path may be an absolute path or relative to the current working directory.\n\n" +
40
- "To use the current directory, you can use: `--root-path .` (i.e. a period for the argument)"
41
-
42
- method_option :trigger, :type => :string, :required => true, :aliases => ['-t', '--triggers'],
43
- :desc => "Triggers to perform. e.g. 'trigger_a,trigger_b'"
44
- method_option :config_file, :type => :string, :default => '', :aliases => '-c',
45
- :desc => "Path to your config.rb file. " +
46
- "Absolute, or relative to --root-path."
47
- method_option :root_path, :type => :string, :default => '', :aliases => '-r',
48
- :desc => "Root path to base all relative path on. " +
49
- "Absolute or relative to current directory (#{Dir.pwd})."
50
- method_option :data_path, :type => :string, :default => '', :aliases => '-d',
51
- :desc => "Path to store persisted data (storage 'keep' data). " +
52
- "Absolute, or relative to --root-path."
53
- method_option :log_path, :type => :string, :default => '', :aliases => '-l',
54
- :desc => "Path to store Backup's log file. " +
55
- "Absolute, or relative to --root-path."
56
- method_option :cache_path, :type => :string, :default => '',
57
- :desc => "Path to store Dropbox's cached authorization. " +
58
- "Absolute, or relative to --root-path."
59
- method_option :tmp_path, :type => :string, :default => '',
60
- :desc => "Path to store temporary data during the backup process. " +
61
- "Absolute, or relative to --root-path."
29
+
30
+ long_desc <<-EOS.gsub(/^ +/, '')
31
+ Performs the backup for the specified trigger(s).
32
+
33
+ You may perform multiple backups by providing multiple triggers,
34
+ separated by commas. Each will run in the order specified.
35
+
36
+ $ backup perform --triggers backup1,backup2,backup3,backup4
37
+
38
+ --root-path may be an absolute path or relative to the current directory.
39
+
40
+ To use the current directory, use: `--root-path .`
41
+
42
+ Relative paths given for --config-file, --data-path, --log-path,
43
+ --cache-path and --tmp-path will be relative to --root-path.
44
+
45
+ Console log output may be forced using --no-quiet.
46
+
47
+ Logging to file or syslog may be disabled using --no-logfile or --no-syslog
48
+ respectively. This will override logging options set in `config.rb`.
49
+ EOS
50
+
51
+ method_option :trigger,
52
+ :aliases => ['-t', '--triggers'],
53
+ :required => true,
54
+ :type => :string,
55
+ :desc => "Triggers to perform. e.g. 'trigger_a,trigger_b'"
56
+
57
+ method_option :config_file,
58
+ :aliases => '-c',
59
+ :type => :string,
60
+ :default => '',
61
+ :desc => 'Path to your config.rb file.'
62
+
63
+ method_option :root_path,
64
+ :aliases => '-r',
65
+ :type => :string,
66
+ :default => '',
67
+ :desc => 'Root path to base all relative path on.'
68
+
69
+ method_option :data_path,
70
+ :aliases => '-d',
71
+ :type => :string,
72
+ :default => '',
73
+ :desc => 'Path to store storage cycling data.'
74
+
75
+ method_option :log_path,
76
+ :aliases => '-l',
77
+ :type => :string,
78
+ :default => '',
79
+ :desc => "Path to store Backup's log file."
80
+
81
+ method_option :cache_path,
82
+ :type => :string,
83
+ :default => '',
84
+ :desc => "Path to store Dropbox's cached authorization."
85
+
86
+ method_option :tmp_path,
87
+ :type => :string,
88
+ :default => '',
89
+ :desc => 'Path to store temporary data during the backup.'
90
+
62
91
  # Note that :quiet, :syslog and :logfile are specified as :string types,
63
92
  # so the --no-<option> usage will set the value to nil instead of false.
64
- method_option :quiet, :type => :string, :default => false, :aliases => '-q', :banner => '',
65
- :desc => "Disable console log output. " +
66
- "May be force enabled using --no-quiet."
67
- method_option :syslog, :type => :string, :default => false, :banner => '',
68
- :desc => "Enable logging to syslog. " +
69
- "May be forced disabled using --no-syslog."
70
- method_option :logfile, :type => :string, :default => true, :banner => '',
71
- :desc => "Enable Backup's log file. " +
72
- "May be forced disabled using --no-logfile."
73
- method_option :check, :type => :boolean, :default => false,
74
- :desc => "Check `config.rb` and all Model configuration for errors or warnings."
93
+ method_option :quiet,
94
+ :aliases => '-q',
95
+ :type => :string,
96
+ :default => false,
97
+ :banner => '',
98
+ :desc => 'Disable console log output.'
99
+
100
+ method_option :syslog,
101
+ :type => :string,
102
+ :default => false,
103
+ :banner => '',
104
+ :desc => 'Enable logging to syslog.'
105
+
106
+ method_option :logfile,
107
+ :type => :string,
108
+ :default => true,
109
+ :banner => '',
110
+ :desc => "Enable Backup's log file."
111
+
112
+ method_option :check,
113
+ :type => :boolean,
114
+ :default => false,
115
+ :desc => 'Check configuration for errors or warnings.'
75
116
 
76
117
  def perform
77
- ##
78
- # Prepare to perform requested backup jobs.
118
+ check if options[:check] # this will exit()
119
+
79
120
  models = nil
80
121
  begin
81
- ##
82
122
  # Set logger options
83
123
  opts = options
84
124
  Logger.configure do
85
- console.quiet = opts[:quiet]
86
- logfile.enabled = opts[:logfile]
87
- logfile.log_path = opts[:log_path]
88
- syslog.enabled = opts[:syslog]
125
+ console.quiet = opts[:quiet]
126
+ logfile.enabled = opts[:logfile]
127
+ logfile.log_path = opts[:log_path]
128
+ syslog.enabled = opts[:syslog]
89
129
  end
90
130
 
91
- ##
92
131
  # Update Config variables
93
132
  # (config_file, root_path, data_path, cache_path, tmp_path)
94
133
  Config.update(options)
95
134
 
96
- ##
97
- # Ensure the :cache_path and :tmp_path are created
98
- # if they do not yet exist
99
- [Config.cache_path, Config.tmp_path].each do |path|
100
- FileUtils.mkdir_p(path)
101
- end
102
-
103
- ##
104
135
  # Load the user's +config.rb+ file (and all their Models).
105
- # May update Logger options.
136
+ # May update Logger (and Config) options.
106
137
  Config.load_config!
107
138
 
108
- ##
109
139
  # Identify all Models to be run for the given +triggers+.
110
140
  triggers = options[:trigger].split(',').map(&:strip)
111
141
  models = triggers.map {|trigger|
112
142
  Model.find_by_trigger(trigger)
113
143
  }.flatten.uniq
114
144
 
115
- if models.empty?
116
- raise Errors::CLIError,
117
- "No Models found for trigger(s) '#{triggers.join(',')}'."
118
- end
145
+ raise Errors::CLIError, "No Models found for trigger(s) " +
146
+ "'#{ triggers.join(',') }'." if models.empty?
119
147
 
120
- if options[:check] && Logger.has_warnings?
121
- raise Errors::CLIError, 'Configuration Check has warnings.'
122
- end
123
-
124
- ##
125
- # Finalize Logger configuration and begin real-time logging.
148
+ # Finalize Logger and begin real-time logging.
126
149
  Logger.start!
127
150
 
128
151
  rescue Exception => err
129
152
  Logger.error Errors::CLIError.wrap(err)
130
- Logger.error 'Configuration Check Failed.' if options[:check]
131
153
  # Logger configuration will be ignored
132
154
  # and messages will be output to the console only.
133
155
  Logger.abort!
134
- exit(options[:check] ? 1 : 3)
156
+ exit(3)
135
157
  end
136
158
 
137
- if options[:check]
138
- Logger.info 'Configuration Check Succeeded.'
159
+ # Model#perform! handles all exceptions from this point,
160
+ # as each model may fail and return here to allow others to run.
161
+ warnings = errors = false
162
+ models.each do |model|
163
+ model.perform!
164
+ warnings ||= Logger.has_warnings?
165
+ errors ||= Logger.has_errors?
166
+ Logger.clear!
167
+ end
168
+ exit(errors ? 2 : 1) if errors || warnings
169
+ end
170
+
171
+ ##
172
+ # [Check]
173
+ #
174
+ # Loads the user's `config.rb` (and all Model files) and reports any Errors
175
+ # or Warnings. This is primarily for checking for syntax errors, missing
176
+ # dependencies and deprecation warnings.
177
+ #
178
+ # This may also be invoked using the `--check` option to `backup perform`.
179
+ #
180
+ # This command only requires `Config.config_file` to be correct.
181
+ # All other Config paths are irrelevant.
182
+ #
183
+ # All output will be sent to the console only.
184
+ # Logger options will be ignored.
185
+ #
186
+ # If successful, this method with exit(0).
187
+ # If there are Errors or Warnings, it will exit(1).
188
+ desc 'check', 'Check for configuration errors or warnings'
189
+
190
+ long_desc <<-EOS.gsub(/^ +/, '')
191
+ Loads your 'config.rb' file and all models and reports any
192
+ errors or warnings with your configuration, including missing
193
+ dependencies and the use of any deprecated settings.
194
+ EOS
195
+
196
+ method_option :config_file,
197
+ :aliases => '-c',
198
+ :type => :string,
199
+ :default => '',
200
+ :desc => "Path to your config.rb file."
201
+
202
+ def check
203
+ begin
204
+ Config.update(options)
205
+ Config.load_config!
206
+ rescue Exception => err
207
+ Logger.error Errors::CLIError.wrap(err)
208
+ end
209
+
210
+ if Logger.has_warnings? || Logger.has_errors?
211
+ Logger.error 'Configuration Check Failed.'
212
+ exit_code = 1
139
213
  else
140
- ##
141
- # Perform the backup job for each Model found for the given triggers,
142
- # clearing the Logger after each job.
143
- #
144
- # Model#perform! handles all exceptions from this point,
145
- # as each model may fail and return here to allow others to run.
146
- warnings = errors = false
147
- models.each do |model|
148
- model.perform!
149
- warnings ||= Logger.has_warnings?
150
- errors ||= Logger.has_errors?
151
- Logger.clear!
152
- end
153
- exit(errors ? 2 : 1) if errors || warnings
214
+ Logger.info 'Configuration Check Succeeded.'
215
+ exit_code = 0
154
216
  end
217
+
218
+ Logger.abort!
219
+ exit(exit_code)
155
220
  end
156
221
 
157
222
  ##
@@ -161,14 +226,28 @@ module Backup
161
226
  # $ backup generate:model --trigger my_backup --databases='mongodb'
162
227
  # will generate a pre-populated model with a base MongoDB setup
163
228
  desc 'generate:model', "Generates a Backup model file."
164
- long_desc "Generates a Backup model file.\n\n" +
165
- "\s\sNote: '--config-path' is the path to the directory where 'config.rb' is located.\n\n" +
166
- "\s\sThe model file will be created as '<config_path>/models/<trigger>.rb'\n\n" +
167
- "\s\sDefault: #{Config.root_path}\n\n"
168
229
 
169
- method_option :trigger, :type => :string, :required => true, :aliases => '-t'
170
- method_option :config_path, :type => :string,
171
- :desc => 'Path to your Backup configuration directory'
230
+ long_desc <<-EOS.gsub(/^ +/, '')
231
+ Generates a Backup model file.
232
+
233
+ '--config-path' is the path to the *directory* where 'config.rb' is located.
234
+
235
+ The model file will be created as '<config_path>/models/<trigger>.rb'
236
+
237
+ The default location would be:
238
+
239
+ #{ Config.root_path }/models/
240
+ EOS
241
+
242
+ method_option :trigger,
243
+ :aliases => '-t',
244
+ :required => true,
245
+ :type => :string,
246
+ :desc => 'Trigger name for the Backup model'
247
+
248
+ method_option :config_path,
249
+ :type => :string,
250
+ :desc => 'Path to your Backup configuration directory'
172
251
 
173
252
  # options with their available values
174
253
  %w{ databases storages syncers
@@ -178,9 +257,13 @@ module Backup
178
257
  "(#{Dir[path + '/*'].sort.map {|p| File.basename(p) }.join(', ')})"
179
258
  end
180
259
 
181
- method_option :archives, :type => :boolean
182
- method_option :splitter, :type => :boolean, :default => true,
183
- :desc => "use `--no-splitter` to disable"
260
+ method_option :archives,
261
+ :type => :boolean,
262
+ :desc => 'Model will include tar archives.'
263
+ method_option :splitter,
264
+ :type => :boolean,
265
+ :default => true,
266
+ :desc => "Use `--no-splitter` to disable"
184
267
 
185
268
  define_method "generate:model" do
186
269
  opts = options.merge(
@@ -193,8 +276,12 @@ module Backup
193
276
  config = File.join(config_path, "config.rb")
194
277
  model = File.join(models_path, "#{opts[:trigger]}.rb")
195
278
 
279
+ if File.file?(config_path)
280
+ abort('--config-path should be a directory, not a file.')
281
+ end
282
+
196
283
  FileUtils.mkdir_p(models_path)
197
- if overwrite?(model)
284
+ if Helpers.overwrite?(model)
198
285
  File.open(model, 'w') do |file|
199
286
  file.write(
200
287
  Backup::Template.new({:options => opts}).result("cli/model.erb")
@@ -203,7 +290,7 @@ module Backup
203
290
  puts "Generated model file: '#{ model }'."
204
291
  end
205
292
 
206
- if not File.exist?(config)
293
+ unless File.exist?(config)
207
294
  File.open(config, "w") do |file|
208
295
  file.write(Backup::Template.new.result("cli/config"))
209
296
  end
@@ -215,8 +302,18 @@ module Backup
215
302
  # [Generate:Config]
216
303
  # Generates the main configuration file
217
304
  desc 'generate:config', 'Generates the main Backup bootstrap/configuration file'
218
- method_option :config_path, :type => :string,
219
- :desc => "Path to your Backup configuration directory. Default: #{Config.root_path}"
305
+
306
+ long_desc <<-EOS.gsub(/^ +/, '')
307
+ Path to your Backup configuration directory.
308
+
309
+ Default path would be:
310
+
311
+ #{ Config.root_path }
312
+ EOS
313
+
314
+ method_option :config_path,
315
+ :type => :string,
316
+ :desc => 'Path to your Backup configuration directory.'
220
317
 
221
318
  define_method 'generate:config' do
222
319
  config_path = options[:config_path] ?
@@ -224,7 +321,7 @@ module Backup
224
321
  config = File.join(config_path, "config.rb")
225
322
 
226
323
  FileUtils.mkdir_p(config_path)
227
- if overwrite?(config)
324
+ if Helpers.overwrite?(config)
228
325
  File.open(config, "w") do |file|
229
326
  file.write(Backup::Template.new.result("cli/config"))
230
327
  end
@@ -247,13 +344,20 @@ module Backup
247
344
  case options[:encryptor].downcase
248
345
  when 'openssl'
249
346
  base64 = options[:base64] ? '-base64' : ''
250
- password = options[:password_file].empty? ? '' : "-pass file:#{options[:password_file]}"
347
+ password = options[:password_file].empty? ? '' :
348
+ "-pass file:#{ options[:password_file] }"
251
349
  salt = options[:salt] ? '-salt' : ''
252
- %x[openssl aes-256-cbc -d #{base64} #{password} #{salt} -in '#{options[:in]}' -out '#{options[:out]}']
350
+
351
+ Helpers.exec!(
352
+ "openssl aes-256-cbc -d #{ base64 } #{ password } #{ salt } " +
353
+ "-in '#{ options[:in] }' -out '#{ options[:out] }'"
354
+ )
253
355
  when 'gpg'
254
- %x[gpg -o '#{options[:out]}' -d '#{options[:in]}']
356
+ Helpers.exec!(
357
+ "gpg -o '#{ options[:out] }' -d '#{ options[:in] }'"
358
+ )
255
359
  else
256
- puts "Unknown encryptor: #{options[:encryptor]}"
360
+ puts "Unknown encryptor: #{ options[:encryptor] }"
257
361
  puts "Use either 'openssl' or 'gpg'."
258
362
  end
259
363
  end
@@ -262,24 +366,27 @@ module Backup
262
366
  # [Dependencies]
263
367
  # Returns a list of Backup's dependencies
264
368
  desc 'dependencies', 'Display, Check or Install Dependencies for Backup.'
265
- long_desc 'Display the list of dependencies for Backup, check the installation status, or install them through Backup.'
266
- method_option :install, :type => :string
369
+
370
+ long_desc <<-EOS.gsub(/^ +/, '')
371
+ To display a list of available dependencies, run:
372
+
373
+ $ backup dependencies --list
374
+
375
+ To install one of these dependencies, run:
376
+
377
+ $ backup dependencies --install <name>
378
+
379
+ To check if a dependency is already installed, run:
380
+
381
+ $ backup dependencies --installed <name>
382
+ EOS
383
+
384
+ method_option :install, :type => :string, :banner => 'NAME'
267
385
  method_option :list, :type => :boolean
268
- method_option :installed, :type => :string
386
+ method_option :installed, :type => :string, :banner => 'NAME'
269
387
 
270
388
  def dependencies
271
- unless options.any?
272
- puts
273
- puts "To display a list of available dependencies, run:\n\n"
274
- puts " backup dependencies --list"
275
- puts
276
- puts "To install one of these dependencies (with the correct version), run:\n\n"
277
- puts " backup dependencies --install <name>"
278
- puts
279
- puts "To check if a dependency is already installed, run:\n\n"
280
- puts " backup dependencies --installed <name>"
281
- exit
282
- end
389
+ Helpers.exec!("#{ $0 } help dependencies") unless options.any?
283
390
 
284
391
  if options[:list]
285
392
  deps = Dependency.all
@@ -361,23 +468,27 @@ module Backup
361
468
  puts "Backup #{Backup::Version.current}"
362
469
  end
363
470
 
364
- private
365
-
366
- ##
367
- # Helper method for asking the user if he/she wants to overwrite the file
368
- def overwrite?(path)
369
- if File.exist?(path)
370
- return yes? "A file already exists at '#{ path }'. Do you want to overwrite? [y/n]"
371
- end
372
- true
373
- end
374
-
375
471
  # This is to avoid Thor's warnings when stubbing methods on the Thor class.
376
472
  module Helpers
377
473
  class << self
474
+
475
+ def overwrite?(path)
476
+ return true unless File.exist?(path)
477
+
478
+ $stderr.print "A file already exists at '#{ path }'.\n" +
479
+ "Do you want to overwrite? [y/n] "
480
+ /^[Yy]/ =~ $stdin.gets
481
+ end
482
+
483
+ def exec!(cmd)
484
+ puts "Lauching: #{ cmd }"
485
+ exec(cmd)
486
+ end
487
+
378
488
  def bundler_loaded?
379
489
  !ENV['BUNDLE_GEMFILE'].to_s.empty?
380
490
  end
491
+
381
492
  end
382
493
  end
383
494