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.
@@ -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