nimbu 0.4 → 0.5.1

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.
@@ -0,0 +1,32 @@
1
+ require "nimbu/command/base"
2
+
3
+ # interacting with your sites (list, create)
4
+ #
5
+ class Nimbu::Command::Sites < Nimbu::Command::Base
6
+ # index
7
+ #
8
+ # list sites you can edit
9
+ #
10
+ def index
11
+ sites = nimbu.sites.list
12
+ if sites.respond_to?(:any?) && sites.any?
13
+ display "\nYou have access to following sites:\n"
14
+ sites.each do |site|
15
+ display " - #{site.name.white.bold} => http://#{site.domain}"
16
+ end
17
+ else
18
+ display "You don't have access to any Nimbu sites."
19
+ display ""
20
+ display "Visit http://nimbu.io, start your 30-day trial and discover our amazing platform!"
21
+ end
22
+ end
23
+
24
+ # index
25
+ #
26
+ # list sites you can edit
27
+ #
28
+ def list
29
+ return index
30
+ end
31
+ end
32
+
@@ -9,17 +9,17 @@ class Nimbu::Command::Themes < Nimbu::Command::Base
9
9
  # list available commands or display help for a specific command
10
10
  #
11
11
  def index
12
- themes = json_decode(nimbu.list_themes)
12
+ themes = nimbu.themes(:subdomain => Nimbu::Auth.site).list
13
13
  if themes.any?
14
- puts "You have following themes for this website:"
14
+ display "\nYou have following themes for this website:"
15
15
  themes.each do |theme|
16
- puts " - #{theme['theme']['name']} (#{theme['theme']['id']})"
16
+ puts " - #{theme.name.bold} (#{theme.short})"
17
17
  end
18
18
  else
19
19
  puts "Hm. You seem to have no themes. Is that normal?"
20
20
  end
21
21
  puts ""
22
- puts "Currently this directory is configured for '#{Nimbu::Auth.theme}'"
22
+ puts "Currently this directory is configured for '#{Nimbu::Auth.theme.red.bold}'"
23
23
  end
24
24
 
25
25
  # list
@@ -33,57 +33,34 @@ class Nimbu::Command::Themes < Nimbu::Command::Base
33
33
  else
34
34
  theme = Nimbu::Auth.theme
35
35
  end
36
- display "Showing layouts, templates and assets for '#{theme}':"
37
- contents = json_decode(nimbu.show_theme_contents(theme))
38
- contents["layouts"].each do |l|
39
- display " - layouts/#{l["name"]}"
40
- end unless contents["layouts"].nil?
41
- contents["templates"].each do |t|
42
- display " - templates/#{t["name"]}"
43
- end unless contents["templates"].nil?
44
- contents["snippets"].each do |s|
45
- display " - snippets/#{s["name"]}"
46
- end unless contents["templates"].nil?
47
- contents["assets"].each do |a|
48
- display " - #{a["folder"]}/#{a["name"]}"
49
- end unless contents["assets"].nil?
50
- end
51
-
52
- # download
53
- #
54
- # download all layouts, templates and assets
55
- #
56
- def download
57
- input = args.shift.downcase rescue nil
58
- if !input.to_s.strip.empty?
59
- theme = input.to_s.strip
60
- else
61
- theme = Nimbu::Auth.theme
62
- end
63
- display "Downloading layouts, templates and assets for '#{theme}':"
64
- contents = json_decode(nimbu.show_theme_contents(theme))
65
- contents["layouts"].each do |asset|
66
- print " - layouts/#{asset["name"]}"
67
- data = json_decode(nimbu.fetch_theme_layout(theme,asset["id"]))
68
- filename = File.join(Dir.pwd,"layouts",asset["name"])
69
- FileUtils.mkdir_p(File.dirname(filename))
70
- File.open(filename, 'w') do |file|
71
- file.puts(data["code"])
36
+ display "\nShowing layouts, templates, snippets and assets for '#{theme.red.bold}':"
37
+ contents = nimbu.themes(:subdomain => Nimbu::Auth.site).get(theme)
38
+ if contents["layouts"].any?
39
+ display "\nLayouts:".bold
40
+ contents["layouts"].each do |l|
41
+ display " - layouts/#{l["name"]}"
72
42
  end
43
+ end
73
44
 
74
- print " (ok)\n"
45
+ if contents["templates"].any?
46
+ display "\nTemplates:".bold
47
+ contents["templates"].each do |t|
48
+ display " - templates/#{t["name"]}"
49
+ end
75
50
  end
76
51
 
77
- contents["templates"].each do |asset|
78
- print " - templates/#{asset["name"]}"
79
- data = json_decode(nimbu.fetch_theme_template(theme,asset["id"]))
80
- filename = File.join(Dir.pwd,"templates",asset["name"])
81
- FileUtils.mkdir_p(File.dirname(filename))
82
- File.open(filename, 'w') do |file|
83
- file.puts(data["code"])
52
+ if contents["snippets"].any?
53
+ display "\nSnippets:".bold
54
+ contents["snippets"].each do |s|
55
+ display " - snippets/#{s["name"]}"
84
56
  end
57
+ end
85
58
 
86
- print " (ok)\n"
59
+ if contents["assets"].any?
60
+ display "\nAssets:".bold
61
+ contents["assets"].each do |a|
62
+ display " - #{a["folder"]}/#{a["name"]}"
63
+ end
87
64
  end
88
65
  end
89
66
 
@@ -103,7 +80,7 @@ class Nimbu::Command::Themes < Nimbu::Command::Base
103
80
  # end
104
81
  theme = Nimbu::Auth.theme
105
82
  display "Pushing layouts, templates and assets for '#{theme}' to the server:"
106
-
83
+
107
84
  layouts_glob = Dir.glob("#{Dir.pwd}/layouts/**/*.liquid")
108
85
  layouts_files = layouts_glob.map {|dir| dir.gsub("#{Dir.pwd}/layouts/","")}
109
86
  templates_glob = Dir.glob("#{Dir.pwd}/templates/**/*.liquid")
@@ -119,7 +96,7 @@ class Nimbu::Command::Themes < Nimbu::Command::Base
119
96
  print " - layouts/#{layout}"
120
97
  nimbu.upload_layout(theme, layout, IO.read(file))
121
98
  print " (ok)\n"
122
- end
99
+ end
123
100
 
124
101
  print "\nTemplates:\n"
125
102
  templates_files.each do |template|
@@ -181,7 +158,7 @@ class Nimbu::Command::Themes < Nimbu::Command::Base
181
158
  end
182
159
  end
183
160
 
184
- private
161
+ private
185
162
 
186
163
  def anyFileWithWord?(glob,word)
187
164
  found = false
data/lib/nimbu/helpers.rb CHANGED
@@ -2,6 +2,9 @@ require "vendor/nimbu/okjson"
2
2
 
3
3
  module Nimbu
4
4
  module Helpers
5
+
6
+ extend self
7
+
5
8
  def home_directory
6
9
  running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME']
7
10
  end
@@ -19,7 +22,7 @@ module Nimbu
19
22
  puts(msg)
20
23
  else
21
24
  print(msg)
22
- STDOUT.flush
25
+ $stdout.flush
23
26
  end
24
27
  end
25
28
 
@@ -27,50 +30,40 @@ module Nimbu
27
30
  display("\r\e[0K#{line}", line_break)
28
31
  end
29
32
 
30
- def deprecate(version)
31
- display "!!! DEPRECATION WARNING: This command will be removed in version #{version}"
32
- display
33
- end
34
-
35
- def error(msg)
36
- STDERR.puts(format_with_bang(msg))
37
- exit 1
33
+ def deprecate(message)
34
+ display "WARNING: #{message}"
38
35
  end
39
36
 
40
37
  def confirm_billing
41
38
  display
42
39
  display "This action will cause your account to be billed at the end of the month"
43
- display "For more information, see http://devcenter.nimbu.com/articles/billing"
44
- display "Are you sure you want to do this? (y/n) ", false
45
- if ask.downcase == 'y'
46
- nimbu.confirm_billing
47
- return true
40
+ display "For more information, see https://devcenter.Nimbu.com/articles/usage-and-billing"
41
+ if confirm
42
+ Nimbu::Auth.client.confirm_billing
43
+ true
48
44
  end
49
45
  end
50
46
 
51
- def confirm(message="Are you sure you wish to continue? (y/n)?")
47
+ def confirm(message="Are you sure you wish to continue? (y/n)")
52
48
  display("#{message} ", false)
53
- ask.downcase == 'y'
49
+ ['y', 'yes'].include?(ask.downcase)
54
50
  end
55
51
 
56
52
  def confirm_command(app_to_confirm = app, message=nil)
57
- raise(Nimbu::Command::CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") unless app_to_confirm
58
-
59
- if respond_to?(:extract_option) && confirmed_app = extract_option('--confirm', false)
53
+ if confirmed_app = Nimbu::Command.current_options[:confirm]
60
54
  unless confirmed_app == app_to_confirm
61
55
  raise(Nimbu::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app_to_confirm}.")
62
56
  end
63
57
  return true
64
58
  else
65
59
  display
66
- message ||= "WARNING: Potentially Destructive Action\nThis command will affect the app: #{app_to_confirm}"
60
+ message ||= "WARNING: Destructive Action\nThis command will affect the app: #{app_to_confirm}"
67
61
  message << "\nTo proceed, type \"#{app_to_confirm}\" or re-run this command with --confirm #{app_to_confirm}"
68
62
  output_with_bang(message)
69
63
  display
70
64
  display "> ", false
71
65
  if ask.downcase != app_to_confirm
72
- output_with_bang "Input did not match #{app_to_confirm}. Aborted."
73
- false
66
+ error("Confirmation did not match #{app_to_confirm}. Aborted.")
74
67
  else
75
68
  true
76
69
  end
@@ -78,12 +71,12 @@ module Nimbu
78
71
  end
79
72
 
80
73
  def format_date(date)
81
- date = Time.parse(date) if date.is_a?(String)
82
- date.strftime("%Y-%m-%d %H:%M %Z")
74
+ date = Time.parse(date).utc if date.is_a?(String)
75
+ date.strftime("%Y-%m-%d %H:%M %Z").gsub('GMT', 'UTC')
83
76
  end
84
77
 
85
78
  def ask
86
- STDIN.gets.strip
79
+ $stdin.gets.to_s.strip
87
80
  end
88
81
 
89
82
  def shell(cmd)
@@ -117,14 +110,22 @@ module Nimbu
117
110
  %x{ git #{flattened_args} 2>&1 }.strip
118
111
  end
119
112
 
120
- def time_ago(elapsed)
121
- if elapsed < 60
122
- "#{elapsed.floor}s ago"
123
- elsif elapsed < (60 * 60)
124
- "#{(elapsed / 60).floor}m ago"
125
- else
126
- "#{(elapsed / 60 / 60).floor}h ago"
113
+ def time_ago(since)
114
+ if since.is_a?(String)
115
+ since = Time.parse(since)
116
+ end
117
+
118
+ elapsed = Time.now - since
119
+
120
+ message = since.strftime("%Y/%m/%d %H:%M:%S")
121
+ if elapsed <= 60
122
+ message << " (~ #{elapsed.floor}s ago)"
123
+ elsif elapsed <= (60 * 60)
124
+ message << " (~ #{(elapsed / 60).floor}m ago)"
125
+ elsif elapsed <= (60 * 60 * 25)
126
+ message << " (~ #{(elapsed / 60 / 60).floor}h ago)"
127
127
  end
128
+ message
128
129
  end
129
130
 
130
131
  def truncate(text, length)
@@ -152,7 +153,6 @@ module Nimbu
152
153
  end
153
154
 
154
155
  def create_git_remote(remote, url)
155
- return unless has_git?
156
156
  return if git('remote').split("\n").include?(remote)
157
157
  return unless File.exists?(".git")
158
158
  git "remote add #{remote} #{url}"
@@ -169,30 +169,33 @@ module Nimbu
169
169
  header = headers[index]
170
170
  lengths << longest([header].concat(objects.map { |o| o[column].to_s }))
171
171
  end
172
+ lines = lengths.map {|length| "-" * length}
173
+ lengths[-1] = 0 # remove padding from last column
172
174
  display_row headers, lengths
173
- display_row lengths.map { |length| "-" * length }, lengths
175
+ display_row lines, lengths
174
176
  objects.each do |row|
175
177
  display_row columns.map { |column| row[column] }, lengths
176
178
  end
177
179
  end
178
180
 
179
181
  def display_row(row, lengths)
182
+ row_data = []
180
183
  row.zip(lengths).each do |column, length|
181
- format = column.is_a?(Fixnum) ? "%#{length}s " : "%-#{length}s "
182
- display format % column, false
184
+ format = column.is_a?(Fixnum) ? "%#{length}s" : "%-#{length}s"
185
+ row_data << format % column
183
186
  end
184
- display
187
+ display(row_data.join(" "))
185
188
  end
186
189
 
187
190
  def json_encode(object)
188
191
  Nimbu::OkJson.encode(object)
189
- rescue Nimbu::OkJson::ParserError
192
+ rescue Nimbu::OkJson::Error
190
193
  nil
191
194
  end
192
195
 
193
196
  def json_decode(json)
194
197
  Nimbu::OkJson.decode(json)
195
- rescue Nimbu::OkJson::ParserError
198
+ rescue Nimbu::OkJson::Error
196
199
  nil
197
200
  end
198
201
 
@@ -207,7 +210,7 @@ module Nimbu
207
210
  end
208
211
 
209
212
  def with_tty(&block)
210
- return unless $stdin.tty?
213
+ return unless $stdin.isatty
211
214
  begin
212
215
  yield
213
216
  rescue
@@ -216,7 +219,7 @@ module Nimbu
216
219
  end
217
220
 
218
221
  def get_terminal_environment
219
- { "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`, "LINES" => `tput lines` }
222
+ { "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`.strip, "LINES" => `tput lines`.strip }
220
223
  rescue
221
224
  { "TERM" => ENV["TERM"] }
222
225
  end
@@ -227,30 +230,24 @@ module Nimbu
227
230
 
228
231
  ## DISPLAY HELPERS
229
232
 
230
- def action(message)
231
- output_with_arrow("#{message}... ", false)
232
- Nimbu::Helpers.enable_error_capture
233
- yield
234
- Nimbu::Helpers.disable_error_capture
235
- display "done", false
236
- display(", #{@status}", false) if @status
233
+ def action(message, options={})
234
+ display("#{message}... ", false)
235
+ Nimbu::Helpers.error_with_failure = true
236
+ ret = yield
237
+ Nimbu::Helpers.error_with_failure = false
238
+ display((options[:success] || "done"), false)
239
+ if @status
240
+ display(", #{@status}", false)
241
+ @status = nil
242
+ end
237
243
  display
244
+ ret
238
245
  end
239
246
 
240
247
  def status(message)
241
248
  @status = message
242
249
  end
243
250
 
244
- def output(message="", new_line=true)
245
- return if message.to_s.strip == ""
246
- display(" " + message.split("\n").join("\n "), new_line)
247
- end
248
-
249
- def output_with_arrow(message="", new_line=true)
250
- return if message.to_s.strip == ""
251
- display("----> " + message.split("\n").join("\n "), new_line)
252
- end
253
-
254
251
  def format_with_bang(message)
255
252
  return '' if message.to_s.strip == ""
256
253
  " ! " + message.split("\n").join("\n ! ")
@@ -261,10 +258,21 @@ module Nimbu
261
258
  display(format_with_bang(message), new_line)
262
259
  end
263
260
 
264
- def error_with_failure(message)
265
- display "failed"
266
- output_with_bang(message)
267
- exit 1
261
+ def error(message)
262
+ if Nimbu::Helpers.error_with_failure
263
+ display("failed")
264
+ Nimbu::Helpers.error_with_failure = false
265
+ end
266
+ $stderr.puts(format_with_bang(message))
267
+ exit(1)
268
+ end
269
+
270
+ def self.error_with_failure
271
+ @@error_with_failure ||= false
272
+ end
273
+
274
+ def self.error_with_failure=(new_error_with_failure)
275
+ @@error_with_failure = new_error_with_failure
268
276
  end
269
277
 
270
278
  def self.included_into
@@ -283,31 +291,6 @@ module Nimbu
283
291
  extended_into << base
284
292
  end
285
293
 
286
- def self.enable_error_capture
287
- included_into.each do |base|
288
- base.send(:alias_method, :error_without_failure, :error)
289
- base.send(:alias_method, :error, :error_with_failure)
290
- end
291
- extended_into.each do |base|
292
- class << base
293
- alias_method :error_without_failure, :error
294
- alias_method :error, :error_with_failure
295
- end
296
- end
297
- end
298
-
299
- def self.disable_error_capture
300
- included_into.each do |base|
301
- base.send(:alias_method, :error, :error_without_failure)
302
- end
303
- extended_into.each do |base|
304
- class << base
305
- alias_method :error, :error_without_failure
306
- end
307
- end
308
- end
309
-
310
-
311
294
  def display_header(message="", new_line=true)
312
295
  return if message.to_s.strip == ""
313
296
  display("=== " + message.to_s.split("\n").join("\n=== "), new_line)
@@ -341,7 +324,128 @@ module Nimbu
341
324
 
342
325
  def hprint(string='')
343
326
  Kernel.print(string)
344
- STDOUT.flush
327
+ $stdout.flush
328
+ end
329
+
330
+ def spinner(ticks)
331
+ %w(/ - \\ |)[ticks % 4]
332
+ end
333
+
334
+ def launchy(message, url)
335
+ action(message) do
336
+ require("launchy")
337
+ launchy = Launchy.open(url)
338
+ if launchy.respond_to?(:join)
339
+ launchy.join
340
+ end
341
+ end
342
+ end
343
+
344
+ # produces a printf formatter line for an array of items
345
+ # if an individual line item is an array, it will create columns
346
+ # that are lined-up
347
+ #
348
+ # line_formatter(["foo", "barbaz"]) # => "%-6s"
349
+ # line_formatter(["foo", "barbaz"], ["bar", "qux"]) # => "%-3s %-6s"
350
+ #
351
+ def line_formatter(array)
352
+ if array.any? {|item| item.is_a?(Array)}
353
+ cols = []
354
+ array.each do |item|
355
+ if item.is_a?(Array)
356
+ item.each_with_index { |val,idx| cols[idx] = [cols[idx]||0, (val || '').length].max }
357
+ end
358
+ end
359
+ cols.map { |col| "%-#{col}s" }.join(" ")
360
+ else
361
+ "%s"
362
+ end
363
+ end
364
+
365
+ def styled_array(array, options={})
366
+ fmt = line_formatter(array)
367
+ array = array.sort unless options[:sort] == false
368
+ array.each do |element|
369
+ display((fmt % element).rstrip)
370
+ end
371
+ display
372
+ end
373
+
374
+ def format_error(error, message='Nimbu client internal error.')
375
+ formatted_error = []
376
+ formatted_error << " ! #{message}"
377
+ formatted_error << ' ! Search for help at: https://help.Nimbu.com'
378
+ formatted_error << ' ! Or report a bug at: https://github.com/Nimbu/Nimbu/issues/new'
379
+ formatted_error << ''
380
+ formatted_error << " Error: #{error.message} (#{error.class})"
381
+ formatted_error << " Backtrace: #{error.backtrace.first}"
382
+ error.backtrace[1..-1].each do |line|
383
+ formatted_error << " #{line}"
384
+ end
385
+ if error.backtrace.length > 1
386
+ formatted_error << ''
387
+ end
388
+ command = ARGV.map do |arg|
389
+ if arg.include?(' ')
390
+ arg = %{"#{arg}"}
391
+ else
392
+ arg
393
+ end
394
+ end.join(' ')
395
+ formatted_error << " Command: Nimbu #{command}"
396
+ require 'Nimbu/auth'
397
+ unless Nimbu::Auth.host == Nimbu::Auth.default_host
398
+ formatted_error << " Host: #{Nimbu::Auth.host}"
399
+ end
400
+ if http_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
401
+ formatted_error << " HTTP Proxy: #{http_proxy}"
402
+ end
403
+ if https_proxy = ENV['https_proxy'] || ENV['HTTPS_PROXY']
404
+ formatted_error << " HTTPS Proxy: #{https_proxy}"
405
+ end
406
+ formatted_error << " Version: #{Nimbu.user_agent}"
407
+ formatted_error << "\n"
408
+ formatted_error.join("\n")
409
+ end
410
+
411
+ def styled_error(error, message='Nimbu client internal error.')
412
+ if Nimbu::Helpers.error_with_failure
413
+ display("failed")
414
+ Nimbu::Helpers.error_with_failure = false
415
+ end
416
+ $stderr.puts(format_error(error, message))
417
+ end
418
+
419
+ def styled_header(header)
420
+ display("=== #{header}")
421
+ end
422
+
423
+ def styled_hash(hash, keys=nil)
424
+ max_key_length = hash.keys.map {|key| key.to_s.length}.max + 2
425
+ keys ||= hash.keys.sort {|x,y| x.to_s <=> y.to_s}
426
+ keys.each do |key|
427
+ case value = hash[key]
428
+ when Array
429
+ if value.empty?
430
+ next
431
+ else
432
+ elements = value.sort {|x,y| x.to_s <=> y.to_s}
433
+ display("#{key}: ".ljust(max_key_length), false)
434
+ display(elements[0])
435
+ elements[1..-1].each do |element|
436
+ display("#{' ' * max_key_length}#{element}")
437
+ end
438
+ if elements.length > 1
439
+ display
440
+ end
441
+ end
442
+ when nil
443
+ next
444
+ else
445
+ display("#{key}: ".ljust(max_key_length), false)
446
+ display(value)
447
+ end
448
+ end
345
449
  end
346
450
 
347
451
  def string_distance(first, last)
@@ -378,5 +482,80 @@ module Nimbu
378
482
  distances[first.length][last.length]
379
483
  end
380
484
 
485
+ def suggestion(actual, possibilities)
486
+ distances = Hash.new {|hash,key| hash[key] = []}
487
+
488
+ possibilities.each do |suggestion|
489
+ distances[string_distance(actual, suggestion)] << suggestion
490
+ end
491
+
492
+ minimum_distance = distances.keys.min
493
+ if minimum_distance < 4
494
+ suggestions = distances[minimum_distance].sort
495
+ if suggestions.length == 1
496
+ "Perhaps you meant `#{suggestions.first}`."
497
+ else
498
+ "Perhaps you meant #{suggestions[0...-1].map {|suggestion| "`#{suggestion}`"}.join(', ')} or `#{suggestions.last}`."
499
+ end
500
+ else
501
+ nil
502
+ end
503
+ end
504
+
505
+ module System
506
+ # Cross-platform web browser command; respects the value set in $BROWSER.
507
+ #
508
+ # Returns an array, e.g.: ['open']
509
+ def browser_launcher
510
+ browser = ENV['BROWSER'] || (
511
+ osx? ? 'open' : windows? ? %w[cmd /c start] :
512
+ %w[xdg-open cygstart x-www-browser firefox opera mozilla netscape].find { |comm| which comm }
513
+ )
514
+
515
+ abort "Please set $BROWSER to a web launcher to use this command." unless browser
516
+ Array(browser)
517
+ end
518
+
519
+ def osx?
520
+ require 'rbconfig'
521
+ RbConfig::CONFIG['host_os'].to_s.include?('darwin')
522
+ end
523
+
524
+ def windows?
525
+ require 'rbconfig'
526
+ RbConfig::CONFIG['host_os'] =~ /msdos|mswin|djgpp|mingw|windows/
527
+ end
528
+
529
+ # Cross-platform way of finding an executable in the $PATH.
530
+ #
531
+ # which('ruby') #=> /usr/bin/ruby
532
+ def which(cmd)
533
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
534
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
535
+ exts.each { |ext|
536
+ exe = "#{path}/#{cmd}#{ext}"
537
+ return exe if File.executable? exe
538
+ }
539
+ end
540
+ return nil
541
+ end
542
+
543
+ # Checks whether a command exists on this system in the $PATH.
544
+ #
545
+ # name - The String name of the command to check for.
546
+ #
547
+ # Returns a Boolean.
548
+ def command?(name)
549
+ !which(name).nil?
550
+ end
551
+
552
+ def tmp_dir
553
+ ENV['TMPDIR'] || ENV['TEMP'] || '/tmp'
554
+ end
555
+ end
556
+
557
+ include System
558
+ extend System
559
+
381
560
  end
382
- end
561
+ end