nimbu 0.4 → 0.5.1

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