cf 0.1.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.
- data/LICENSE +30 -0
- data/Rakefile +1 -0
- data/bin/cf +14 -0
- data/lib/cf.rb +2 -0
- data/lib/cf/cli.rb +249 -0
- data/lib/cf/cli/app.rb +545 -0
- data/lib/cf/cli/command.rb +433 -0
- data/lib/cf/cli/dots.rb +130 -0
- data/lib/cf/cli/service.rb +91 -0
- data/lib/cf/cli/user.rb +71 -0
- data/lib/cf/constants.rb +10 -0
- data/lib/cf/detect.rb +60 -0
- data/lib/cf/plugin.rb +24 -0
- data/lib/cf/version.rb +3 -0
- data/plugins/manifests/main.rb +508 -0
- metadata +105 -0
@@ -0,0 +1,433 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "interact"
|
3
|
+
require "json/pure"
|
4
|
+
|
5
|
+
require "conduit"
|
6
|
+
|
7
|
+
require "cf/constants"
|
8
|
+
require "cf/cli/dots"
|
9
|
+
|
10
|
+
|
11
|
+
module CF
|
12
|
+
module Interactive
|
13
|
+
include ::Interactive::Rewindable
|
14
|
+
include Dots
|
15
|
+
|
16
|
+
class InteractiveDefault
|
17
|
+
def initialize(query)
|
18
|
+
@query = query
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"(interaction)"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_proc
|
26
|
+
@query
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def force?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def ask(question, options = {})
|
35
|
+
if force? and options.key?(:default)
|
36
|
+
options[:default]
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def list_choices(choices, options)
|
43
|
+
choices.each_with_index do |o, i|
|
44
|
+
puts "#{c(i + 1, :green)}: #{o}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def input_state(options)
|
49
|
+
CFState.new(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def prompt(question, options)
|
53
|
+
value =
|
54
|
+
case options[:default]
|
55
|
+
when true
|
56
|
+
"y"
|
57
|
+
when false
|
58
|
+
"n"
|
59
|
+
when nil
|
60
|
+
""
|
61
|
+
else
|
62
|
+
options[:default].to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
print "#{question}"
|
66
|
+
print c("> ", :blue)
|
67
|
+
|
68
|
+
unless value.empty?
|
69
|
+
print "#{c(value, :black) + "\b" * value.size}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def handler(which, state)
|
74
|
+
ans = state.answer
|
75
|
+
pos = state.position
|
76
|
+
|
77
|
+
if state.default?
|
78
|
+
if which.is_a?(Array) and which[0] == :key
|
79
|
+
# initial non-movement keypress clears default answer
|
80
|
+
clear_input(state)
|
81
|
+
else
|
82
|
+
# wipe away any coloring
|
83
|
+
redraw_input(state)
|
84
|
+
end
|
85
|
+
|
86
|
+
state.clear_default!
|
87
|
+
end
|
88
|
+
|
89
|
+
super
|
90
|
+
|
91
|
+
print "\n" if which == :enter
|
92
|
+
end
|
93
|
+
|
94
|
+
class CFState < Interactive::InputState
|
95
|
+
def initialize(options = {}, answer = nil, position = 0)
|
96
|
+
@options = options
|
97
|
+
|
98
|
+
if answer
|
99
|
+
@answer = answer
|
100
|
+
elsif options[:default]
|
101
|
+
case options[:default]
|
102
|
+
when true
|
103
|
+
@answer = "y"
|
104
|
+
when false
|
105
|
+
@answer = "n"
|
106
|
+
else
|
107
|
+
@answer = options[:default].to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
@default = true
|
111
|
+
else
|
112
|
+
@answer = ""
|
113
|
+
end
|
114
|
+
|
115
|
+
@position = position
|
116
|
+
@done = false
|
117
|
+
end
|
118
|
+
|
119
|
+
def clear_default!
|
120
|
+
@default = false
|
121
|
+
end
|
122
|
+
|
123
|
+
def default?
|
124
|
+
@default
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Command < Thor
|
130
|
+
include Interactive
|
131
|
+
|
132
|
+
def self.flag(name, options = {}, &query)
|
133
|
+
if query
|
134
|
+
options[:default] ||= InteractiveDefault.new(query)
|
135
|
+
end
|
136
|
+
|
137
|
+
method_option(name, options)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.callbacks_for(name)
|
141
|
+
cs = callbacks[name]
|
142
|
+
if superclass.respond_to? :callbacks
|
143
|
+
cs.merge superclass.callbacks_for(name)
|
144
|
+
else
|
145
|
+
cs
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.callbacks
|
150
|
+
@callbacks ||= Hash.new do |h, name|
|
151
|
+
h[name] = Hash.new do |h, task|
|
152
|
+
h[task] = []
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.add_callback(name, task, callback)
|
158
|
+
callbacks[name][task] << callback
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.before(task, &callback)
|
162
|
+
add_callback(:before, task, callback)
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.after(task, &callback)
|
166
|
+
add_callback(:after, task, callback)
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.ensuring(task, &callback)
|
170
|
+
add_callback(:ensuring, task, callback)
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.around(task, &callback)
|
174
|
+
add_callback(:around, task, callback)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def callbacks_for(what)
|
180
|
+
self.class.callbacks_for(what)
|
181
|
+
end
|
182
|
+
|
183
|
+
def passed_value(flag)
|
184
|
+
if (val = options[flag]) && \
|
185
|
+
!val.is_a?(CF::Interactive::InteractiveDefault)
|
186
|
+
val
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def input(name, *args)
|
191
|
+
@inputs ||= {}
|
192
|
+
return @inputs[name] if @inputs.key?(name)
|
193
|
+
|
194
|
+
if options[name].respond_to? :to_proc
|
195
|
+
@inputs[name] = instance_exec(*args, &options[name])
|
196
|
+
else
|
197
|
+
@inputs[name] = options[name]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def forget(name)
|
202
|
+
@inputs.delete name
|
203
|
+
end
|
204
|
+
|
205
|
+
def script?
|
206
|
+
if options.key?("script")
|
207
|
+
options["script"]
|
208
|
+
else
|
209
|
+
!$stdout.tty?
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def force?
|
214
|
+
if options.key?("force")
|
215
|
+
options["force"]
|
216
|
+
else
|
217
|
+
script?
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def simple_output?
|
222
|
+
if options.key?("simple_output")
|
223
|
+
options["simple_output"]
|
224
|
+
else
|
225
|
+
script?
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def color?
|
230
|
+
if options.key?("color")
|
231
|
+
options["color"]
|
232
|
+
else
|
233
|
+
!simple_output?
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def err(msg, exit_status = 1)
|
238
|
+
if script?
|
239
|
+
$stderr.puts(msg)
|
240
|
+
else
|
241
|
+
puts c(msg, :red)
|
242
|
+
end
|
243
|
+
|
244
|
+
$exit_status = 1
|
245
|
+
end
|
246
|
+
|
247
|
+
def invoke_task(task, args)
|
248
|
+
callbacks_for(:before)[task.name.to_sym].each do |c|
|
249
|
+
c.call
|
250
|
+
end
|
251
|
+
|
252
|
+
action = proc do |*new_args|
|
253
|
+
if new_args.empty?
|
254
|
+
task.run(self, args)
|
255
|
+
elsif new_args.first.is_a? Array
|
256
|
+
task.run(self, new_args.first)
|
257
|
+
else
|
258
|
+
task.run(self, new_args)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
callbacks_for(:around)[task.name.to_sym].each do |a|
|
263
|
+
before = action
|
264
|
+
action = proc do |passed_args|
|
265
|
+
# with more than one wrapper, when the outer wrapper passes args to
|
266
|
+
# inner wrapper, which calls the next inner with no args, it should
|
267
|
+
# get the args passed to it by outer
|
268
|
+
args = passed_args if passed_args
|
269
|
+
instance_exec(before, args, &a)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
res = instance_exec(args, &action)
|
274
|
+
|
275
|
+
callbacks_for(:after)[task.name.to_sym].each do |c|
|
276
|
+
c.call
|
277
|
+
end
|
278
|
+
|
279
|
+
res
|
280
|
+
rescue Interrupt
|
281
|
+
$exit_status = 130
|
282
|
+
rescue Thor::Error
|
283
|
+
raise
|
284
|
+
rescue Exception => e
|
285
|
+
msg = e.class.name
|
286
|
+
msg << ": #{e}" unless e.to_s.empty?
|
287
|
+
err msg
|
288
|
+
|
289
|
+
ensure_config_dir
|
290
|
+
|
291
|
+
File.open(File.expand_path(CF::CRASH_FILE), "w") do |f|
|
292
|
+
f.print "Time of crash:\n "
|
293
|
+
f.puts Time.now
|
294
|
+
f.puts ""
|
295
|
+
f.puts msg
|
296
|
+
f.puts ""
|
297
|
+
|
298
|
+
e.backtrace.each do |loc|
|
299
|
+
if loc =~ /\/gems\//
|
300
|
+
f.puts loc.sub(/.*\/gems\//, "")
|
301
|
+
else
|
302
|
+
f.puts loc.sub(File.expand_path("../../../..", __FILE__) + "/", "")
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
ensure
|
307
|
+
callbacks_for(:ensuring)[task.name.to_sym].each do |c|
|
308
|
+
c.call
|
309
|
+
end
|
310
|
+
end
|
311
|
+
public :invoke_task
|
312
|
+
|
313
|
+
def sane_target_url(url)
|
314
|
+
unless url =~ /^https?:\/\//
|
315
|
+
url = "http://#{url}"
|
316
|
+
end
|
317
|
+
|
318
|
+
url.gsub(/\/$/, "")
|
319
|
+
end
|
320
|
+
|
321
|
+
def target_file
|
322
|
+
one_of(CF::TARGET_FILE, CF::OLD_TARGET_FILE)
|
323
|
+
end
|
324
|
+
|
325
|
+
def tokens_file
|
326
|
+
one_of(CF::TOKENS_FILE, CF::OLD_TOKENS_FILE)
|
327
|
+
end
|
328
|
+
|
329
|
+
def one_of(*paths)
|
330
|
+
first = nil
|
331
|
+
paths.each do |p|
|
332
|
+
exp = File.expand_path(p)
|
333
|
+
first ||= exp
|
334
|
+
return exp if File.exist? exp
|
335
|
+
end
|
336
|
+
|
337
|
+
first
|
338
|
+
end
|
339
|
+
|
340
|
+
def client_target
|
341
|
+
File.read(target_file).chomp
|
342
|
+
end
|
343
|
+
|
344
|
+
def ensure_config_dir
|
345
|
+
config = File.expand_path(CF::CONFIG_DIR)
|
346
|
+
Dir.mkdir(config) unless File.exist? config
|
347
|
+
end
|
348
|
+
|
349
|
+
def set_target(url)
|
350
|
+
ensure_config_dir
|
351
|
+
|
352
|
+
File.open(File.expand_path(CF::TARGET_FILE), "w") do |f|
|
353
|
+
f.write(sane_target_url(url))
|
354
|
+
end
|
355
|
+
|
356
|
+
@client = nil
|
357
|
+
end
|
358
|
+
|
359
|
+
def tokens
|
360
|
+
JSON.parse(File.read(tokens_file))
|
361
|
+
end
|
362
|
+
|
363
|
+
def client_token
|
364
|
+
tokens[client_target]
|
365
|
+
end
|
366
|
+
|
367
|
+
def save_tokens(ts)
|
368
|
+
ensure_config_dir
|
369
|
+
|
370
|
+
File.open(File.expand_path(CF::TOKENS_FILE), "w") do |f|
|
371
|
+
f.write ts.to_json
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def save_token(token)
|
376
|
+
ts = tokens
|
377
|
+
ts[client_target] = token
|
378
|
+
save_tokens(ts)
|
379
|
+
end
|
380
|
+
|
381
|
+
def remove_token
|
382
|
+
ts = tokens
|
383
|
+
ts.delete client_target
|
384
|
+
save_tokens(ts)
|
385
|
+
end
|
386
|
+
|
387
|
+
def client
|
388
|
+
@client ||= Conduit::Client.new(client_target, client_token)
|
389
|
+
end
|
390
|
+
|
391
|
+
def usage(used, limit)
|
392
|
+
"#{b(human_size(used))} of #{b(human_size(limit, 0))}"
|
393
|
+
end
|
394
|
+
|
395
|
+
def percentage(num, low = 50, mid = 70)
|
396
|
+
color =
|
397
|
+
if num <= low
|
398
|
+
:green
|
399
|
+
elsif num <= mid
|
400
|
+
:yellow
|
401
|
+
else
|
402
|
+
:red
|
403
|
+
end
|
404
|
+
|
405
|
+
c(format("%.1f\%", num), color)
|
406
|
+
end
|
407
|
+
|
408
|
+
def megabytes(str)
|
409
|
+
if str =~ /T$/i
|
410
|
+
str.to_i * 1024 * 1024
|
411
|
+
elsif str =~ /G$/i
|
412
|
+
str.to_i * 1024
|
413
|
+
elsif str =~ /M$/i
|
414
|
+
str.to_i
|
415
|
+
elsif str =~ /K$/i
|
416
|
+
str.to_i / 1024
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def human_size(num, precision = 1)
|
421
|
+
sizes = ["G", "M", "K"]
|
422
|
+
sizes.each.with_index do |suf, i|
|
423
|
+
pow = sizes.size - i
|
424
|
+
unit = 1024 ** pow
|
425
|
+
if num >= unit
|
426
|
+
return format("%.#{precision}f%s", num / unit, suf)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
format("%.#{precision}fB", num)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
data/lib/cf/cli/dots.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
module CF
|
2
|
+
module Dots
|
3
|
+
DOT_COUNT = 3
|
4
|
+
DOT_TICK = 0.15
|
5
|
+
|
6
|
+
class Skipper
|
7
|
+
def initialize(&ret)
|
8
|
+
@return = ret
|
9
|
+
end
|
10
|
+
|
11
|
+
def skip(&callback)
|
12
|
+
@return.call("SKIPPED", :yellow, callback)
|
13
|
+
end
|
14
|
+
|
15
|
+
def give_up(&callback)
|
16
|
+
@return.call("GAVE UP", :red, callback)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fail(&callback)
|
20
|
+
@return.call("FAILED", :red, callback)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_progress(message)
|
25
|
+
unless simple_output?
|
26
|
+
print message
|
27
|
+
dots!
|
28
|
+
end
|
29
|
+
|
30
|
+
skipper = Skipper.new do |status, color, callback|
|
31
|
+
unless simple_output?
|
32
|
+
stop_dots!
|
33
|
+
puts "... #{c(status, color)}"
|
34
|
+
end
|
35
|
+
|
36
|
+
return callback && callback.call
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
res = yield skipper
|
41
|
+
unless simple_output?
|
42
|
+
stop_dots!
|
43
|
+
puts "... #{c("OK", :green)}"
|
44
|
+
end
|
45
|
+
res
|
46
|
+
rescue
|
47
|
+
unless simple_output?
|
48
|
+
stop_dots!
|
49
|
+
puts "... #{c("FAILED", :red)}"
|
50
|
+
end
|
51
|
+
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def color?
|
57
|
+
$stdout.tty?
|
58
|
+
end
|
59
|
+
|
60
|
+
COLOR_CODES = {
|
61
|
+
:black => 0,
|
62
|
+
:red => 1,
|
63
|
+
:green => 2,
|
64
|
+
:yellow => 3,
|
65
|
+
:blue => 4,
|
66
|
+
:magenta => 5,
|
67
|
+
:cyan => 6,
|
68
|
+
:white => 7
|
69
|
+
}
|
70
|
+
|
71
|
+
# colored text
|
72
|
+
def c(str, color, bright = true)
|
73
|
+
return str unless color?
|
74
|
+
"\e[#{bright ? 9 : 3}#{COLOR_CODES[color]}m#{str}\e[0m"
|
75
|
+
end
|
76
|
+
module_function :c
|
77
|
+
|
78
|
+
# bold text
|
79
|
+
def b(str)
|
80
|
+
return str unless color?
|
81
|
+
"\e[1m#{str}\e[0m"
|
82
|
+
end
|
83
|
+
module_function :b
|
84
|
+
|
85
|
+
def dots!
|
86
|
+
@dots ||=
|
87
|
+
Thread.new do
|
88
|
+
before_sync = $stdout.sync
|
89
|
+
|
90
|
+
$stdout.sync = true
|
91
|
+
|
92
|
+
printed = false
|
93
|
+
i = 1
|
94
|
+
until @stop_dots
|
95
|
+
if printed
|
96
|
+
print "\b" * DOT_COUNT
|
97
|
+
end
|
98
|
+
|
99
|
+
print ("." * i).ljust(DOT_COUNT)
|
100
|
+
printed = true
|
101
|
+
|
102
|
+
if i == DOT_COUNT
|
103
|
+
i = 0
|
104
|
+
else
|
105
|
+
i += 1
|
106
|
+
end
|
107
|
+
|
108
|
+
sleep DOT_TICK
|
109
|
+
end
|
110
|
+
|
111
|
+
if printed
|
112
|
+
print "\b" * DOT_COUNT
|
113
|
+
print " " * DOT_COUNT
|
114
|
+
print "\b" * DOT_COUNT
|
115
|
+
end
|
116
|
+
|
117
|
+
$stdout.sync = before_sync
|
118
|
+
@stop_dots = nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def stop_dots!
|
123
|
+
return unless @dots
|
124
|
+
return if @stop_dots
|
125
|
+
@stop_dots = true
|
126
|
+
@dots.join
|
127
|
+
@dots = nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|