nimbu 0.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.
- data/bin/nimbu +12 -0
- data/lib/nimbu.rb +6 -0
- data/lib/nimbu/auth.rb +268 -0
- data/lib/nimbu/cli.rb +12 -0
- data/lib/nimbu/client.rb +269 -0
- data/lib/nimbu/client/rendezvous.rb +76 -0
- data/lib/nimbu/command.rb +189 -0
- data/lib/nimbu/command/auth.rb +38 -0
- data/lib/nimbu/command/base.rb +212 -0
- data/lib/nimbu/command/help.rb +139 -0
- data/lib/nimbu/command/helpers.rb +382 -0
- data/lib/nimbu/command/init.rb +38 -0
- data/lib/nimbu/command/server.rb +22 -0
- data/lib/nimbu/command/themes.rb +86 -0
- data/lib/nimbu/helpers.rb +382 -0
- data/lib/nimbu/server/base.rb +82 -0
- data/lib/nimbu/server/views/index.haml +1 -0
- data/lib/nimbu/version.rb +3 -0
- data/lib/vendor/nimbu/okjson.rb +557 -0
- metadata +121 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
require "nimbu/command/base"
|
2
|
+
|
3
|
+
# list commands and display help
|
4
|
+
#
|
5
|
+
class Nimbu::Command::Help < Nimbu::Command::Base
|
6
|
+
|
7
|
+
PRIMARY_NAMESPACES = %w( auth server themes )
|
8
|
+
|
9
|
+
# help [COMMAND]
|
10
|
+
#
|
11
|
+
# list available commands or display help for a specific command
|
12
|
+
#
|
13
|
+
def index
|
14
|
+
if command = args.shift
|
15
|
+
help_for_command(command)
|
16
|
+
else
|
17
|
+
help_for_root
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_command "-h", "help"
|
22
|
+
alias_command "--help", "help"
|
23
|
+
|
24
|
+
def self.usage_for_command(command)
|
25
|
+
command = new.send(:commands)[command]
|
26
|
+
"Usage: nimbu #{command[:banner]}" if command
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def commands_for_namespace(name)
|
32
|
+
Nimbu::Command.commands.values.select do |command|
|
33
|
+
command[:namespace] == name && command[:command] != name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def namespaces
|
38
|
+
namespaces = Nimbu::Command.namespaces
|
39
|
+
namespaces.delete("app")
|
40
|
+
namespaces
|
41
|
+
end
|
42
|
+
|
43
|
+
def commands
|
44
|
+
commands = Nimbu::Command.commands
|
45
|
+
Nimbu::Command.command_aliases.each do |new, old|
|
46
|
+
commands[new] = commands[old].dup
|
47
|
+
commands[new][:command] = new
|
48
|
+
commands[new][:namespace] = nil
|
49
|
+
commands[new][:alias_for] = old
|
50
|
+
end
|
51
|
+
commands
|
52
|
+
end
|
53
|
+
|
54
|
+
def legacy_help_for_namespace(namespace)
|
55
|
+
instance = Nimbu::Command::Help.groups.map do |group|
|
56
|
+
[ group.title, group.select { |c| c.first =~ /^#{namespace}/ }.length ]
|
57
|
+
end.sort_by { |l| l.last }.last
|
58
|
+
return nil unless instance
|
59
|
+
return nil if instance.last.zero?
|
60
|
+
instance.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def legacy_help_for_command(command)
|
64
|
+
Nimbu::Command::Help.groups.each do |group|
|
65
|
+
group.each do |cmd, description|
|
66
|
+
return description if cmd.split(" ").first == command
|
67
|
+
end
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def primary_namespaces
|
73
|
+
PRIMARY_NAMESPACES.map { |name| namespaces[name] }.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def additional_namespaces
|
77
|
+
(namespaces.values - primary_namespaces)
|
78
|
+
end
|
79
|
+
|
80
|
+
def summary_for_namespaces(namespaces)
|
81
|
+
size = longest(namespaces.map { |n| n[:name] })
|
82
|
+
namespaces.sort_by {|namespace| namespace[:name]}.each do |namespace|
|
83
|
+
name = namespace[:name]
|
84
|
+
namespace[:description] ||= legacy_help_for_namespace(name)
|
85
|
+
puts " %-#{size}s # %s" % [ name, namespace[:description] ]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def help_for_root
|
90
|
+
puts "Usage: nimbu COMMAND [command-specific-options]"
|
91
|
+
puts
|
92
|
+
puts "Primary help topics, type \"nimbu help TOPIC\" for more details:"
|
93
|
+
puts
|
94
|
+
summary_for_namespaces(primary_namespaces)
|
95
|
+
puts
|
96
|
+
puts "Additional topics:"
|
97
|
+
puts
|
98
|
+
summary_for_namespaces(additional_namespaces)
|
99
|
+
puts
|
100
|
+
end
|
101
|
+
|
102
|
+
def help_for_namespace(name)
|
103
|
+
namespace_commands = commands_for_namespace(name)
|
104
|
+
|
105
|
+
unless namespace_commands.empty?
|
106
|
+
size = longest(namespace_commands.map { |c| c[:banner] })
|
107
|
+
namespace_commands.sort_by { |c| c[:banner].to_s }.each do |command|
|
108
|
+
next if command[:help] =~ /DEPRECATED/
|
109
|
+
command[:summary] ||= legacy_help_for_command(command[:command])
|
110
|
+
puts " %-#{size}s # %s" % [ command[:banner], command[:summary] ]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def help_for_command(name)
|
116
|
+
command = commands[name]
|
117
|
+
|
118
|
+
if command
|
119
|
+
puts "Usage: nimbu #{command[:banner]}"
|
120
|
+
|
121
|
+
if command[:help].strip.length > 0
|
122
|
+
puts command[:help].split("\n")[1..-1].join("\n")
|
123
|
+
else
|
124
|
+
puts
|
125
|
+
puts " " + legacy_help_for_command(name).to_s
|
126
|
+
end
|
127
|
+
puts
|
128
|
+
end
|
129
|
+
|
130
|
+
if commands_for_namespace(name).size > 0
|
131
|
+
puts "Additional commands, type \"nimbu help COMMAND\" for more details:"
|
132
|
+
puts
|
133
|
+
help_for_namespace(name)
|
134
|
+
puts
|
135
|
+
elsif command.nil?
|
136
|
+
error "#{name} is not a nimbu command. See 'nimbu help'."
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
require "vendor/nimbu/okjson"
|
2
|
+
|
3
|
+
module Nimbu
|
4
|
+
module Helpers
|
5
|
+
def home_directory
|
6
|
+
running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME']
|
7
|
+
end
|
8
|
+
|
9
|
+
def running_on_windows?
|
10
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
11
|
+
end
|
12
|
+
|
13
|
+
def running_on_a_mac?
|
14
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
15
|
+
end
|
16
|
+
|
17
|
+
def display(msg="", new_line=true)
|
18
|
+
if new_line
|
19
|
+
puts(msg)
|
20
|
+
else
|
21
|
+
print(msg)
|
22
|
+
STDOUT.flush
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def redisplay(line, line_break = false)
|
27
|
+
display("\r\e[0K#{line}", line_break)
|
28
|
+
end
|
29
|
+
|
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
|
38
|
+
end
|
39
|
+
|
40
|
+
def confirm_billing
|
41
|
+
display
|
42
|
+
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
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def confirm(message="Are you sure you wish to continue? (y/n)?")
|
52
|
+
display("#{message} ", false)
|
53
|
+
ask.downcase == 'y'
|
54
|
+
end
|
55
|
+
|
56
|
+
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)
|
60
|
+
unless confirmed_app == app_to_confirm
|
61
|
+
raise(Nimbu::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app_to_confirm}.")
|
62
|
+
end
|
63
|
+
return true
|
64
|
+
else
|
65
|
+
display
|
66
|
+
message ||= "WARNING: Potentially Destructive Action\nThis command will affect the app: #{app_to_confirm}"
|
67
|
+
message << "\nTo proceed, type \"#{app_to_confirm}\" or re-run this command with --confirm #{app_to_confirm}"
|
68
|
+
output_with_bang(message)
|
69
|
+
display
|
70
|
+
display "> ", false
|
71
|
+
if ask.downcase != app_to_confirm
|
72
|
+
output_with_bang "Input did not match #{app_to_confirm}. Aborted."
|
73
|
+
false
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_date(date)
|
81
|
+
date = Time.parse(date) if date.is_a?(String)
|
82
|
+
date.strftime("%Y-%m-%d %H:%M %Z")
|
83
|
+
end
|
84
|
+
|
85
|
+
def ask
|
86
|
+
STDIN.gets.strip
|
87
|
+
end
|
88
|
+
|
89
|
+
def shell(cmd)
|
90
|
+
FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_command(command, args=[])
|
94
|
+
Nimbu::Command.run(command, args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def retry_on_exception(*exceptions)
|
98
|
+
retry_count = 0
|
99
|
+
begin
|
100
|
+
yield
|
101
|
+
rescue *exceptions => ex
|
102
|
+
raise ex if retry_count >= 3
|
103
|
+
sleep 3
|
104
|
+
retry_count += 1
|
105
|
+
retry
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_git?
|
110
|
+
%x{ git --version }
|
111
|
+
$?.success?
|
112
|
+
end
|
113
|
+
|
114
|
+
def git(args)
|
115
|
+
return "" unless has_git?
|
116
|
+
flattened_args = [args].flatten.compact.join(" ")
|
117
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
118
|
+
end
|
119
|
+
|
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"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def truncate(text, length)
|
131
|
+
if text.size > length
|
132
|
+
text[0, length - 2] + '..'
|
133
|
+
else
|
134
|
+
text
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
@@kb = 1024
|
139
|
+
@@mb = 1024 * @@kb
|
140
|
+
@@gb = 1024 * @@mb
|
141
|
+
def format_bytes(amount)
|
142
|
+
amount = amount.to_i
|
143
|
+
return '(empty)' if amount == 0
|
144
|
+
return amount if amount < @@kb
|
145
|
+
return "#{(amount / @@kb).round}k" if amount < @@mb
|
146
|
+
return "#{(amount / @@mb).round}M" if amount < @@gb
|
147
|
+
return "#{(amount / @@gb).round}G"
|
148
|
+
end
|
149
|
+
|
150
|
+
def quantify(string, num)
|
151
|
+
"%d %s" % [ num, num.to_i == 1 ? string : "#{string}s" ]
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_git_remote(remote, url)
|
155
|
+
return unless has_git?
|
156
|
+
return if git('remote').split("\n").include?(remote)
|
157
|
+
return unless File.exists?(".git")
|
158
|
+
git "remote add #{remote} #{url}"
|
159
|
+
display "Git remote #{remote} added"
|
160
|
+
end
|
161
|
+
|
162
|
+
def longest(items)
|
163
|
+
items.map { |i| i.to_s.length }.sort.last
|
164
|
+
end
|
165
|
+
|
166
|
+
def display_table(objects, columns, headers)
|
167
|
+
lengths = []
|
168
|
+
columns.each_with_index do |column, index|
|
169
|
+
header = headers[index]
|
170
|
+
lengths << longest([header].concat(objects.map { |o| o[column].to_s }))
|
171
|
+
end
|
172
|
+
display_row headers, lengths
|
173
|
+
display_row lengths.map { |length| "-" * length }, lengths
|
174
|
+
objects.each do |row|
|
175
|
+
display_row columns.map { |column| row[column] }, lengths
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def display_row(row, lengths)
|
180
|
+
row.zip(lengths).each do |column, length|
|
181
|
+
format = column.is_a?(Fixnum) ? "%#{length}s " : "%-#{length}s "
|
182
|
+
display format % column, false
|
183
|
+
end
|
184
|
+
display
|
185
|
+
end
|
186
|
+
|
187
|
+
def json_encode(object)
|
188
|
+
Nimbu::OkJson.encode(object)
|
189
|
+
rescue Nimbu::OkJson::ParserError
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
|
193
|
+
def json_decode(json)
|
194
|
+
Nimbu::OkJson.decode(json)
|
195
|
+
rescue Nimbu::OkJson::ParserError
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def set_buffer(enable)
|
200
|
+
with_tty do
|
201
|
+
if enable
|
202
|
+
`stty icanon echo`
|
203
|
+
else
|
204
|
+
`stty -icanon -echo`
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def with_tty(&block)
|
210
|
+
return unless $stdin.tty?
|
211
|
+
begin
|
212
|
+
yield
|
213
|
+
rescue
|
214
|
+
# fails on windows
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_terminal_environment
|
219
|
+
{ "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`, "LINES" => `tput lines` }
|
220
|
+
rescue
|
221
|
+
{ "TERM" => ENV["TERM"] }
|
222
|
+
end
|
223
|
+
|
224
|
+
def fail(message)
|
225
|
+
raise Nimbu::Command::CommandFailed, message
|
226
|
+
end
|
227
|
+
|
228
|
+
## DISPLAY HELPERS
|
229
|
+
|
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
|
237
|
+
display
|
238
|
+
end
|
239
|
+
|
240
|
+
def status(message)
|
241
|
+
@status = message
|
242
|
+
end
|
243
|
+
|
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
|
+
def format_with_bang(message)
|
255
|
+
return '' if message.to_s.strip == ""
|
256
|
+
" ! " + message.split("\n").join("\n ! ")
|
257
|
+
end
|
258
|
+
|
259
|
+
def output_with_bang(message="", new_line=true)
|
260
|
+
return if message.to_s.strip == ""
|
261
|
+
display(format_with_bang(message), new_line)
|
262
|
+
end
|
263
|
+
|
264
|
+
def error_with_failure(message)
|
265
|
+
display "failed"
|
266
|
+
output_with_bang(message)
|
267
|
+
exit 1
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.included_into
|
271
|
+
@@included_into ||= []
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.extended_into
|
275
|
+
@@extended_into ||= []
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.included(base)
|
279
|
+
included_into << base
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.extended(base)
|
283
|
+
extended_into << base
|
284
|
+
end
|
285
|
+
|
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
|
+
def display_header(message="", new_line=true)
|
312
|
+
return if message.to_s.strip == ""
|
313
|
+
display("=== " + message.to_s.split("\n").join("\n=== "), new_line)
|
314
|
+
end
|
315
|
+
|
316
|
+
def display_object(object)
|
317
|
+
case object
|
318
|
+
when Array
|
319
|
+
# list of objects
|
320
|
+
object.each do |item|
|
321
|
+
display_object(item)
|
322
|
+
end
|
323
|
+
when Hash
|
324
|
+
# if all values are arrays, it is a list with headers
|
325
|
+
# otherwise it is a single header with pairs of data
|
326
|
+
if object.values.all? {|value| value.is_a?(Array)}
|
327
|
+
object.keys.sort_by {|key| key.to_s}.each do |key|
|
328
|
+
display_header(key)
|
329
|
+
display_object(object[key])
|
330
|
+
hputs
|
331
|
+
end
|
332
|
+
end
|
333
|
+
else
|
334
|
+
hputs(object.to_s)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def hputs(string='')
|
339
|
+
Kernel.puts(string)
|
340
|
+
end
|
341
|
+
|
342
|
+
def hprint(string='')
|
343
|
+
Kernel.print(string)
|
344
|
+
STDOUT.flush
|
345
|
+
end
|
346
|
+
|
347
|
+
def string_distance(first, last)
|
348
|
+
distances = [] # 0x0s
|
349
|
+
0.upto(first.length) do |index|
|
350
|
+
distances << [index] + [0] * last.length
|
351
|
+
end
|
352
|
+
distances[0] = 0.upto(last.length).to_a
|
353
|
+
1.upto(last.length) do |last_index|
|
354
|
+
1.upto(first.length) do |first_index|
|
355
|
+
first_char = first[first_index - 1, 1]
|
356
|
+
last_char = last[last_index - 1, 1]
|
357
|
+
if first_char == last_char
|
358
|
+
distances[first_index][last_index] = distances[first_index - 1][last_index - 1] # noop
|
359
|
+
else
|
360
|
+
distances[first_index][last_index] = [
|
361
|
+
distances[first_index - 1][last_index], # deletion
|
362
|
+
distances[first_index][last_index - 1], # insertion
|
363
|
+
distances[first_index - 1][last_index - 1] # substitution
|
364
|
+
].min + 1 # cost
|
365
|
+
if first_index > 1 && last_index > 1
|
366
|
+
first_previous_char = first[first_index - 2, 1]
|
367
|
+
last_previous_char = last[last_index - 2, 1]
|
368
|
+
if first_char == last_previous_char && first_previous_char == last_char
|
369
|
+
distances[first_index][last_index] = [
|
370
|
+
distances[first_index][last_index],
|
371
|
+
distances[first_index - 2][last_index - 2] + 1 # transposition
|
372
|
+
].min
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
distances[first.length][last.length]
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
end
|