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