cli-framework 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cli +1059 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e378c6588d51d7390a41c7fd7c7984fe5708ab44
|
4
|
+
data.tar.gz: e78c30a2b5fc8f5fd69e1a1e85062a337f4fc15b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 74fc1951add43ae050bd186e1f42dbc6af3d8b6b0d363b3a849e77a7aa7a88e201d4c22802eb1a5d4055deed23e42b6683fb3cd2554b7c0b2272d09db38ed5ce
|
7
|
+
data.tar.gz: 1fb29e9a371716a752e927040b2d799ca997df8e5612e18346aec8b258daa40612a34b1d899d7abef46a68e09f2b6beae8ae156a3ad69911b4b6714f60c403b4
|
data/bin/cli
ADDED
@@ -0,0 +1,1059 @@
|
|
1
|
+
# puts here all gem that you will need for the command line
|
2
|
+
# require "xyz"
|
3
|
+
#
|
4
|
+
module CommandLineConfig
|
5
|
+
Name ||= "cli"
|
6
|
+
Version ||= "0.0.1"
|
7
|
+
Color ||= 'yellow'
|
8
|
+
Otherwise ||= "help"
|
9
|
+
BeforeInit ||= ['check.rb'] #execute scripts before initilatizing the cli
|
10
|
+
Special ||= [['-v','version'], ['-help','help']]
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
require "terminal-table"
|
16
|
+
|
17
|
+
module CommandLineUtils
|
18
|
+
extend self
|
19
|
+
|
20
|
+
@rendered = ""
|
21
|
+
|
22
|
+
KEYS = {
|
23
|
+
" " => "space",
|
24
|
+
"\t" => "tab", # or ctrl + I on mac
|
25
|
+
"\r" => "return",
|
26
|
+
"\n" => "linefeed", # or ctrl + J on mac
|
27
|
+
"\e" => "escape",
|
28
|
+
"\e[A" => "up",
|
29
|
+
"\e[B" => "down",
|
30
|
+
"\e[C" => "right",
|
31
|
+
"\e[D" => "left",
|
32
|
+
"\177" => "backspace",
|
33
|
+
"\003" => "ctrl-c", # ctrl + c
|
34
|
+
"\004" => "ctrl-d", # ctrl + d
|
35
|
+
}
|
36
|
+
|
37
|
+
# Read a character the user enters on console. This call is synchronous blocking.
|
38
|
+
# This is taken from: http://www.alecjacobson.com/weblog/?p=75
|
39
|
+
def read_char
|
40
|
+
begin
|
41
|
+
# save previous state of stty
|
42
|
+
old_state = `stty -g`
|
43
|
+
# disable echoing and enable raw (not having to press enter)
|
44
|
+
system "stty raw -echo"
|
45
|
+
c = STDIN.getc.chr
|
46
|
+
# gather next two characters of special keys
|
47
|
+
if(c=="\e")
|
48
|
+
extra_thread = Thread.new{
|
49
|
+
c = c + STDIN.getc.chr
|
50
|
+
c = c + STDIN.getc.chr
|
51
|
+
}
|
52
|
+
# wait just long enough for special keys to get swallowed
|
53
|
+
extra_thread.join(0.00001)
|
54
|
+
# kill thread so not-so-long special keys don't wait on getc
|
55
|
+
extra_thread.kill
|
56
|
+
end
|
57
|
+
rescue => ex
|
58
|
+
puts "#{ex.class}: #{ex.message}"
|
59
|
+
puts ex.backtrace
|
60
|
+
ensure
|
61
|
+
# restore previous state of stty
|
62
|
+
system "stty #{old_state}"
|
63
|
+
end
|
64
|
+
return c
|
65
|
+
end
|
66
|
+
|
67
|
+
# Read a keypress on console. Return the key name (e.g. "space", "a", "B")
|
68
|
+
# Params:
|
69
|
+
# +with_exit_codes+:: +Bool+ whether to throw Interrupts when the user presses
|
70
|
+
# ctrl-c and ctrl-d. (true by default)
|
71
|
+
def read_key with_exit_codes = true, return_char = false
|
72
|
+
char = read_char
|
73
|
+
if with_exit_codes and ( char == "\003" or char == "\004" )
|
74
|
+
self.update_line(0,"Process Interrupted"); print "\n"
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
if return_char then char else char_to_raw char end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get each key the user presses and hand it one by one to the block. Do this
|
81
|
+
# as long as the block returns truthy
|
82
|
+
# Params:
|
83
|
+
# +&block+:: +Proc+ a block that receives a user key and returns truthy or falsy
|
84
|
+
def read_key_while_true return_char = false, &block
|
85
|
+
STDIN.noecho do
|
86
|
+
while true # ask for key and run block until it return false
|
87
|
+
@key = CommandLineUtils.read_key
|
88
|
+
not_a_command = @key =~ /[^[:print:]]/ # return 0 if it not a recoginzed command
|
89
|
+
unless not_a_command == 0 # don't run the block and reask for key if it is not an input or a special in KEYS hash
|
90
|
+
returned_value = block.( @key, return_char )
|
91
|
+
return if returned_value == false
|
92
|
+
else
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the console window size
|
100
|
+
# Returns: [width, height]
|
101
|
+
def winsize
|
102
|
+
STDIN.winsize
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def hide_cursor_during # hides cursor during the execustion of providd block
|
107
|
+
print `tput civis` # hide the cursor
|
108
|
+
yield # run the provided block
|
109
|
+
print `tput cnorm` # finally show the cursor
|
110
|
+
end
|
111
|
+
|
112
|
+
def char_to_raw char
|
113
|
+
KEYS.fetch char, char
|
114
|
+
end
|
115
|
+
|
116
|
+
def carriage_return; "\r" end
|
117
|
+
def line_up; "\e[A" end
|
118
|
+
def clear_line; "\e[0K" end
|
119
|
+
def char_left; "\e[D" end
|
120
|
+
def char_right; "\e[C" end
|
121
|
+
|
122
|
+
# modified by me
|
123
|
+
@lineNumber = 0
|
124
|
+
def update_line returnNumber, line = "" #modifie a line, count from bottom to top
|
125
|
+
if returnNumber > @lineNumber
|
126
|
+
print "\r\033[#{returnNumber - @lineNumber}A"
|
127
|
+
elsif returnNumber < @lineNumber
|
128
|
+
print "\r\033[#{@lineNumber - returnNumber}B"
|
129
|
+
else
|
130
|
+
print "\r"
|
131
|
+
end
|
132
|
+
print "\033[K#{line}"
|
133
|
+
@lineNumber = returnNumber
|
134
|
+
end
|
135
|
+
|
136
|
+
@rendered_lines = []
|
137
|
+
def render_line line
|
138
|
+
@rendered_lines << line
|
139
|
+
puts line
|
140
|
+
end
|
141
|
+
|
142
|
+
def rendered_lines
|
143
|
+
@rendered_lines
|
144
|
+
end
|
145
|
+
|
146
|
+
@question
|
147
|
+
def renderQuestion question,returnBool = false
|
148
|
+
@question = question
|
149
|
+
print question
|
150
|
+
print "\n" if returnBool
|
151
|
+
end
|
152
|
+
|
153
|
+
def answerQuestion question = @question, answer
|
154
|
+
down = @lineNumber
|
155
|
+
print "\r\033[B" * down
|
156
|
+
@lineNumber = 0
|
157
|
+
clear
|
158
|
+
clear 1
|
159
|
+
CommandLineUtils.update_line(0, "#{question} #{answer}")
|
160
|
+
print "\n"
|
161
|
+
end
|
162
|
+
|
163
|
+
# clear the console based on the last text rendered
|
164
|
+
def clear lineToClear = nil
|
165
|
+
# determine how many lines to move up
|
166
|
+
|
167
|
+
numberOfLines = lineToClear || @rendered_lines.length
|
168
|
+
# jump back to the first position and clear the line
|
169
|
+
print carriage_return + ( line_up + clear_line ) * numberOfLines + clear_line
|
170
|
+
@rendered_lines = []
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
class String #extend String class in order to add coloration capabilitise
|
178
|
+
# colorization
|
179
|
+
def colorize(color_code)
|
180
|
+
"\e[#{color_code}m#{self}\e[0m"
|
181
|
+
end
|
182
|
+
|
183
|
+
def bold
|
184
|
+
colorize(1)
|
185
|
+
end
|
186
|
+
|
187
|
+
def color colorName
|
188
|
+
colorize(1) #make it bold first
|
189
|
+
case colorName
|
190
|
+
when "red"
|
191
|
+
colorize(1).colorize(31)
|
192
|
+
when "blue"
|
193
|
+
colorize(1).colorize(34)
|
194
|
+
when "yellow"
|
195
|
+
colorize(1).colorize(33)
|
196
|
+
when "green"
|
197
|
+
colorize(1).colorize(32)
|
198
|
+
when "cyan"
|
199
|
+
colorize(1).colorize(36)
|
200
|
+
when "pink"
|
201
|
+
colorize(1).colorize(35)
|
202
|
+
when "white"
|
203
|
+
colorize(1).colorize(37)
|
204
|
+
when "normal"
|
205
|
+
colorize(0)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class ScreenUtils
|
211
|
+
|
212
|
+
def self.newline(line = "", return_line = false)
|
213
|
+
puts ""
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.removeline(lineNumber = 0)
|
217
|
+
lineNumber.times { print "\r" }
|
218
|
+
print ""
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.read_char
|
222
|
+
begin
|
223
|
+
# save previous state of stty
|
224
|
+
old_state = `stty -g`
|
225
|
+
# disable echoing and enable raw (not having to press enter)
|
226
|
+
system "stty raw -echo"
|
227
|
+
c = STDIN.getc.chr
|
228
|
+
# gather next two characters of special keys
|
229
|
+
if(c=="\e")
|
230
|
+
extra_thread = Thread.new{
|
231
|
+
c = c + STDIN.getc.chr
|
232
|
+
c = c + STDIN.getc.chr
|
233
|
+
}
|
234
|
+
# wait just long enough for special keys to get swallowed
|
235
|
+
extra_thread.join(0.00001)
|
236
|
+
# kill thread so not-so-long special keys don't wait on getc
|
237
|
+
extra_thread.kill
|
238
|
+
end
|
239
|
+
rescue => ex
|
240
|
+
puts "#{ex.class}: #{ex.message}"
|
241
|
+
puts ex.backtrace
|
242
|
+
ensure
|
243
|
+
# restore previous state of stty
|
244
|
+
system "stty #{old_state}"
|
245
|
+
end
|
246
|
+
return c
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
module Input
|
252
|
+
extend self
|
253
|
+
|
254
|
+
def ask params = { question: "select from options below ...", default: "", suggestion: "" }
|
255
|
+
@question = params[:question].bold
|
256
|
+
@dialogue = params[:dialogue]
|
257
|
+
@type = params[:type] # can be 'confirm' - 'password'
|
258
|
+
@defaultColor = params[:color] || CommandLineConfig::Color || "white"
|
259
|
+
|
260
|
+
@dialogue_msg = params[:dialogue] || ""
|
261
|
+
@dialogue_msg = @dialogue_msg.color(@defaultColor)
|
262
|
+
@yesColor = params[:yes_color]; @noColor = params[:no_color]
|
263
|
+
@defaultAnswer = params[:default];
|
264
|
+
|
265
|
+
@answer = @defaultAnswer
|
266
|
+
|
267
|
+
@suggestion = params[:suggestion]
|
268
|
+
# CommandLineUtils.hide_cursor_during do # hide the cursor while block is executed
|
269
|
+
@line = ["#{@dialogue_msg + @question}", " ", @answer]
|
270
|
+
@password = ""; @cursor_position = @answer.length
|
271
|
+
render_line true
|
272
|
+
if @type == "confirm"
|
273
|
+
# @answer
|
274
|
+
CommandLineUtils.hide_cursor_during do # hide the cursor while block is executed
|
275
|
+
CommandLineUtils.read_key_while_true do |key|
|
276
|
+
if key == "return" || key == "Y" || key == "y" || key == "t" || key == "T"
|
277
|
+
confirm true
|
278
|
+
false
|
279
|
+
elsif key == "n" || key == "N" || key == "backspace" || key == "f" || key == "F"
|
280
|
+
confirm false
|
281
|
+
false
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
else
|
286
|
+
@keyed = false ;
|
287
|
+
CommandLineUtils.read_key_while_true do |key|
|
288
|
+
if key == "return"
|
289
|
+
@keyed = true
|
290
|
+
answer
|
291
|
+
elsif key == "backspace"
|
292
|
+
@answer.clear unless @keyed
|
293
|
+
@cursor_position = 0 unless @keyed
|
294
|
+
@keyed = true
|
295
|
+
@answer.chop!
|
296
|
+
@cursor_position -= 1 unless @cursor_position == 0
|
297
|
+
update_line
|
298
|
+
elsif key == "space"
|
299
|
+
@answer.insert(@cursor_position, " ")
|
300
|
+
@keyed = true
|
301
|
+
@cursor_position += 1
|
302
|
+
update_line
|
303
|
+
elsif key == "left" && @cursor_position > 0
|
304
|
+
|
305
|
+
print "\033[1D"
|
306
|
+
@cursor_position -= 1
|
307
|
+
|
308
|
+
elsif key == "right"
|
309
|
+
unless @cursor_position == @answer.length
|
310
|
+
print "\033[1C"
|
311
|
+
@cursor_position += 1
|
312
|
+
end
|
313
|
+
else
|
314
|
+
@answer.insert(@cursor_position, key)
|
315
|
+
@cursor_position += key.length
|
316
|
+
@keyed = true
|
317
|
+
update_line
|
318
|
+
end
|
319
|
+
end
|
320
|
+
# end
|
321
|
+
end
|
322
|
+
|
323
|
+
yield(@answer) if block_given?
|
324
|
+
return @answer
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
def render_line stay
|
329
|
+
if @type == "password"
|
330
|
+
print "#{@line[0]+@line[1]}#{'*' * @line[2].length}"
|
331
|
+
else
|
332
|
+
print @line.join('')
|
333
|
+
end
|
334
|
+
print "\n"
|
335
|
+
update_line
|
336
|
+
end
|
337
|
+
|
338
|
+
def update_line
|
339
|
+
if @type == "password"
|
340
|
+
CommandLineUtils.update_line(1, "#{@line[0]+@line[1]}#{'*' * @line[2].length}")
|
341
|
+
else
|
342
|
+
CommandLineUtils.update_line(1, "#{@line.join('')}") #current
|
343
|
+
end
|
344
|
+
if @cursor_position
|
345
|
+
left = @answer.length - @cursor_position
|
346
|
+
print "\033[#{left}D" unless left == 0
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def answer
|
351
|
+
if @type == "password"
|
352
|
+
CommandLineUtils.answerQuestion(@line[0] , ('*' * @line[2].length).color(@defaultColor))
|
353
|
+
else
|
354
|
+
CommandLineUtils.answerQuestion(@line[0] ,@line[2].color(@defaultColor))
|
355
|
+
end
|
356
|
+
return false
|
357
|
+
end
|
358
|
+
|
359
|
+
def confirm bool
|
360
|
+
if bool
|
361
|
+
@answer = true
|
362
|
+
CommandLineUtils.answerQuestion(@line[0] , "yes".color(@yesColor || @defaultColor))
|
363
|
+
else
|
364
|
+
@answer = false
|
365
|
+
CommandLineUtils.answerQuestion(@line[0] , "no".color(@noColor || @defaultColor))
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
module Choice
|
372
|
+
extend self
|
373
|
+
|
374
|
+
def ask params = { question: "select from options below ...", choices: [], default: 0 }
|
375
|
+
# inilialize global params of the list - set default values
|
376
|
+
unless params[:choices].length == 0 # dont launch the selection if no choices are provided
|
377
|
+
params[:multiple] = false unless params[:multiple] == true
|
378
|
+
|
379
|
+
@selector = "❱" # for unselected - on select - unselectable
|
380
|
+
@check_selector = ["⬡ ","⬢ "]
|
381
|
+
@selector = [" "," #{@selector} "]
|
382
|
+
|
383
|
+
@checkbox = params[:multiple]; if @checkbox && params[:indent]; @selector[1].chop!;end ;
|
384
|
+
@checked = @check_selector
|
385
|
+
@checked = ["",""] if !@checkbox
|
386
|
+
@question = params[:question]; @arrayOfChoice = params[:choices]
|
387
|
+
@defaultColor = params[:color] || CommandLineConfig::Color || "white"
|
388
|
+
; @defaultChoiceNumber = params[:default]
|
389
|
+
@dialogue_msg = params[:dialogue] || ""
|
390
|
+
@dialogue_msg = @dialogue_msg.color(@defaultColor)
|
391
|
+
|
392
|
+
CommandLineUtils.hide_cursor_during do # hide the cursor while block is executed
|
393
|
+
|
394
|
+
choice_default_visual
|
395
|
+
@current_line_to_update = @reversedArrayOfChoices[@current_line - 1]
|
396
|
+
|
397
|
+
CommandLineUtils.read_key_while_true do |key|
|
398
|
+
|
399
|
+
if key == "up"
|
400
|
+
|
401
|
+
@previous_line = @current_line;
|
402
|
+
@current_line = 0 if @current_line >= @reversedArrayOfChoices.length # restart at bottom when "up" on top
|
403
|
+
@current_line_to_update = @reversedArrayOfChoices[@current_line]
|
404
|
+
@current_line += 1
|
405
|
+
update_lines
|
406
|
+
|
407
|
+
|
408
|
+
elsif key == "down"
|
409
|
+
|
410
|
+
@previous_line = @current_line
|
411
|
+
@current_line = @reversedArrayOfChoices.length + 1 if @current_line <= 1 # restart at top when "down" on bottom
|
412
|
+
@current_line -= 1;
|
413
|
+
@current_line_to_update = @reversedArrayOfChoices[@current_line - 1]
|
414
|
+
update_lines
|
415
|
+
|
416
|
+
elsif key == "space"
|
417
|
+
|
418
|
+
toggle_select if @checkbox
|
419
|
+
|
420
|
+
elsif key == "return"
|
421
|
+
|
422
|
+
answer
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
key != 'return' && @key != 'escape' && @key != 'backspace' # exit if it returns false
|
427
|
+
end
|
428
|
+
end
|
429
|
+
yield(@index, @choice) if block_given?
|
430
|
+
return @index
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def choice_default_visual
|
435
|
+
CommandLineUtils.renderQuestion(@dialogue_msg + @question.bold, true); @lines = []
|
436
|
+
@arrayOfChoice.each.with_index do |line, index| # print the default selected choice | print layer
|
437
|
+
# inject default values
|
438
|
+
@arrayOfChoice[index][:selectable] = true unless line[:selectable] == false
|
439
|
+
@arrayOfChoice[index][:color] = @defaultColor unless line[:color]
|
440
|
+
@arrayOfChoice[index][:description] = "" unless line[:description]
|
441
|
+
@arrayOfChoice[index][:check] = false unless line[:check] # unless check is defaulted
|
442
|
+
|
443
|
+
# add default visual
|
444
|
+
# line template = ["selector", "check", "name", "description"]
|
445
|
+
line_to_print = [@selector[0], @checked[0], line[:name], line[:description]]
|
446
|
+
|
447
|
+
line_to_print[1] = @checked[1] if @checkbox && @arrayOfChoice[index][:check]
|
448
|
+
line_to_print[0] = @selector[1] if index == @defaultChoiceNumber
|
449
|
+
|
450
|
+
if index == @defaultChoiceNumber
|
451
|
+
CommandLineUtils.render_line "#{(line_to_print[0]+line_to_print[1]+line_to_print[2]).color(line[:color])}#{line_to_print[3]}"
|
452
|
+
elsif !line[:selectable]
|
453
|
+
CommandLineUtils.render_line "#{(line_to_print[0]+line_to_print[1]+line_to_print[2]).color('normal')}#{line_to_print[3]}"
|
454
|
+
else
|
455
|
+
CommandLineUtils.render_line "#{(line_to_print[0]+line_to_print[1]+line_to_print[2]).color('white')}#{line_to_print[3]}"
|
456
|
+
end
|
457
|
+
|
458
|
+
@lines << line_to_print
|
459
|
+
|
460
|
+
end
|
461
|
+
@lines.reverse!
|
462
|
+
@reversedArrayOfChoices = @arrayOfChoice.reverse
|
463
|
+
@current_line = @arrayOfChoice.length - @defaultChoiceNumber # select default line | compute layer
|
464
|
+
@previous_line = @current_line
|
465
|
+
end
|
466
|
+
|
467
|
+
def toggle_select
|
468
|
+
new_check_state = !@current_line_to_update[:check]
|
469
|
+
color = @current_line_to_update[:color]
|
470
|
+
@reversedArrayOfChoices[@current_line - 1][:check] = new_check_state
|
471
|
+
@lines[@current_line - 1][1] = (new_check_state ? @checked[1] : @checked[0])
|
472
|
+
line = @lines[@current_line - 1]
|
473
|
+
CommandLineUtils.update_line(@current_line, "#{(line[0]+line[1]+line[2]).color(color)}#{line[3]}" )#current
|
474
|
+
end
|
475
|
+
|
476
|
+
def update_lines
|
477
|
+
previous_line = @lines[@previous_line - 1];
|
478
|
+
previous_line[0] = @selector[0]
|
479
|
+
current_line = @lines[@current_line - 1];
|
480
|
+
current_line[0] = @selector[1]
|
481
|
+
|
482
|
+
CommandLineUtils.update_line(@current_line, "#{(current_line[0]+current_line[1]+current_line[2]).color(@reversedArrayOfChoices[@current_line - 1][:color])}#{current_line[3]}") #current
|
483
|
+
CommandLineUtils.update_line(@previous_line, "#{(previous_line[0]+previous_line[1]+previous_line[2]).color('white')}#{previous_line[3]}")
|
484
|
+
end
|
485
|
+
|
486
|
+
def answer
|
487
|
+
if @checkbox # :multiple implementation
|
488
|
+
@indexes = []; @choices = []; @answer = false
|
489
|
+
@arrayOfChoice.each.with_index { |choice, index| ; if choice[:check] == true
|
490
|
+
@indexes << index;
|
491
|
+
@choices << choice;
|
492
|
+
name = choice[:as] || choice[:name]
|
493
|
+
if @answer
|
494
|
+
@answer = "#{@answer} - #{name.color(choice[:color])}"
|
495
|
+
else
|
496
|
+
@answer = "#{name.color(choice[:color])}"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
}
|
500
|
+
@answer = "-".color(@defaultColor) unless @answer
|
501
|
+
CommandLineUtils.answerQuestion(@answer)
|
502
|
+
@index = @indexes; @choice = @choices
|
503
|
+
else # normal
|
504
|
+
@index = @reversedArrayOfChoices.length - @current_line.to_i;
|
505
|
+
choices = @reversedArrayOfChoices.reverse
|
506
|
+
@choice = choices[@index];
|
507
|
+
@answer = @choice[:as] || @choice[:name]
|
508
|
+
CommandLineUtils.answerQuestion("#{@answer}".color(@choice[:color]))
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
|
516
|
+
class CommandLineInterface
|
517
|
+
attr_accessor :name, :version, :color
|
518
|
+
|
519
|
+
def initialize
|
520
|
+
@@cmds = []
|
521
|
+
# yield(self) if block_given?
|
522
|
+
@name = CommandLineConfig::Name
|
523
|
+
@version = CommandLineConfig::Version
|
524
|
+
@color = CommandLineConfig::Color || "blue"
|
525
|
+
@otherwise = CommandLineConfig::Otherwise || "help"
|
526
|
+
@commandInput = Inputs.new
|
527
|
+
@first_argument = Inputs.all[0].show if Inputs.all[0]
|
528
|
+
|
529
|
+
|
530
|
+
|
531
|
+
if @first_argument
|
532
|
+
## detect special commands
|
533
|
+
|
534
|
+
redirect_special_cmds @first_argument
|
535
|
+
|
536
|
+
## execute normally
|
537
|
+
|
538
|
+
|
539
|
+
@actionExecute = @first_argument.capitalize + "Action"
|
540
|
+
CommandAction.actions.each do |action| # compare each actions with the first command
|
541
|
+
if action.class.to_s == @actionExecute || action.alias == @first_argument
|
542
|
+
executeCommand action
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
## if action not found
|
547
|
+
|
548
|
+
error
|
549
|
+
else
|
550
|
+
error
|
551
|
+
end
|
552
|
+
|
553
|
+
|
554
|
+
end
|
555
|
+
|
556
|
+
def error
|
557
|
+
@actionExecute = "ErrorAction"
|
558
|
+
CommandAction.actions.each do |action| # compare each actions with the first command
|
559
|
+
if action.class.to_s == @actionExecute
|
560
|
+
executeCommand action
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
def redirect_special_cmds firstArg
|
566
|
+
if CommandLineConfig::Special
|
567
|
+
|
568
|
+
CommandLineConfig::Special.each do |spcmd| #['-v --v', 'version']
|
569
|
+
special_commands = spcmd[0].split(' ')
|
570
|
+
special_commands.each do |special|
|
571
|
+
if special == firstArg
|
572
|
+
@actionExecute = spcmd[1].capitalize + "Action"
|
573
|
+
CommandAction.actions.each do |action| # compare each actions with the first command
|
574
|
+
if action.class.to_s == @actionExecute || action.alias == firstArg
|
575
|
+
executeCommand action
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def executeCommand action
|
585
|
+
inputs = Inputs.all;
|
586
|
+
arguments = make_argumentsObj action
|
587
|
+
options = make_optionsObj action;
|
588
|
+
namespaces = make_namespaceObj action
|
589
|
+
action.action inputs, arguments, options, namespaces
|
590
|
+
exit
|
591
|
+
end
|
592
|
+
|
593
|
+
def make_argumentsObj cmd # create parameters ['type','subtype'] => { 'type': params1, ... args: [rest of the shit]}
|
594
|
+
argumentsObj = {}
|
595
|
+
args = InputArgument.all
|
596
|
+
cmd.args.each do |param|
|
597
|
+
args.delete_at(0)
|
598
|
+
argumentsObj[param] = args[0]
|
599
|
+
|
600
|
+
end
|
601
|
+
argumentsObj['args'] = args
|
602
|
+
return argumentsObj
|
603
|
+
end
|
604
|
+
|
605
|
+
def make_optionsObj cmd # create { "global": true/false, "special": true/false}
|
606
|
+
optionsObj = {}
|
607
|
+
options = InputOption.all
|
608
|
+
cmd.options.each do |opt|
|
609
|
+
opt[0].split(' ').each do |sub|
|
610
|
+
options.each do |input|
|
611
|
+
optionsObj[opt[1]] = true if sub == input
|
612
|
+
end
|
613
|
+
end
|
614
|
+
optionsObj[opt[1]] = false unless optionsObj[opt[1]]
|
615
|
+
end
|
616
|
+
return optionsObj
|
617
|
+
end
|
618
|
+
|
619
|
+
def make_namespaceObj cmd # name:space foo:bar => {'name':'space', 'foo':'bar' }
|
620
|
+
namespaceObj = []
|
621
|
+
InputNamespace.all.each do |namespace|
|
622
|
+
namespaceObj << namespace.split(':')
|
623
|
+
end
|
624
|
+
return namespaceObj
|
625
|
+
end
|
626
|
+
|
627
|
+
def launch commandNameExec
|
628
|
+
@@cmds.each do |cmd|
|
629
|
+
executeCommand(cmd) if cmd.name == commandNameExec
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
|
634
|
+
def self.terminal commandString # call a subprocess that outputs
|
635
|
+
system commandString
|
636
|
+
end
|
637
|
+
|
638
|
+
|
639
|
+
|
640
|
+
|
641
|
+
|
642
|
+
end
|
643
|
+
|
644
|
+
|
645
|
+
class Inputs
|
646
|
+
@@inputs = []
|
647
|
+
def initialize
|
648
|
+
|
649
|
+
if ARGV[0] && ARGV[0].is_namespace? # unNamespace the first arg
|
650
|
+
namespaceEls = ARGV[0].split(':')
|
651
|
+
ARGV.delete_at(0)
|
652
|
+
namespaceEls.each.with_index do |el,index| ; ARGV.insert(0+index, el) ; end
|
653
|
+
|
654
|
+
end
|
655
|
+
ARGV.each.with_index do |arg, index|
|
656
|
+
if arg[0] == "-" #detect options
|
657
|
+
@@inputs << InputOption.new(index, arg)
|
658
|
+
elsif arg.split(':').length >= 2 #detect namespaces
|
659
|
+
@@inputs << InputNamespace.new(index, arg)
|
660
|
+
else
|
661
|
+
@@inputs << InputArgument.new(index, arg)
|
662
|
+
end
|
663
|
+
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def self.all
|
668
|
+
return @@inputs
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
class InputClass
|
673
|
+
def next
|
674
|
+
nextIndex = @index + 1
|
675
|
+
return Inputs.all[nextIndex] if CommandInput.all[nextIndex]
|
676
|
+
end
|
677
|
+
|
678
|
+
def previous
|
679
|
+
prevIndex = @index - 1
|
680
|
+
return Inputs.all[prevIndex] if CommandInput.all[prevIndex]
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
class InputOption < InputClass
|
685
|
+
@@options = []
|
686
|
+
@option
|
687
|
+
def initialize index,arg
|
688
|
+
@@options << arg
|
689
|
+
@option = arg
|
690
|
+
@index = index
|
691
|
+
end
|
692
|
+
|
693
|
+
def show
|
694
|
+
return @option
|
695
|
+
end
|
696
|
+
|
697
|
+
def self.all
|
698
|
+
return @@options
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
class InputNamespace < InputClass
|
703
|
+
@@namespaces = []
|
704
|
+
@namespace
|
705
|
+
def initialize index,arg
|
706
|
+
@@namespaces << arg # => 'arg:space'
|
707
|
+
@namespace = arg
|
708
|
+
@index = index
|
709
|
+
end
|
710
|
+
|
711
|
+
def show
|
712
|
+
return @namespace
|
713
|
+
end
|
714
|
+
|
715
|
+
def self.all
|
716
|
+
return @@namespaces
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
class InputArgument < InputClass
|
721
|
+
@@arguments = []
|
722
|
+
@argument
|
723
|
+
def initialize index,arg
|
724
|
+
@@arguments << arg
|
725
|
+
@argument = arg
|
726
|
+
@index = index
|
727
|
+
end
|
728
|
+
|
729
|
+
def show
|
730
|
+
return @argument
|
731
|
+
end
|
732
|
+
|
733
|
+
def self.all
|
734
|
+
return @@arguments
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
class CommandAction
|
739
|
+
attr_accessor :description, :options, :args, :alias
|
740
|
+
@@actions = []
|
741
|
+
|
742
|
+
def initialize config
|
743
|
+
@description = config[:description]
|
744
|
+
@options = config[:options]
|
745
|
+
@args = config[:params]
|
746
|
+
@alias = config[:alias]
|
747
|
+
|
748
|
+
@@actions << self
|
749
|
+
end
|
750
|
+
|
751
|
+
def self.register config = Hash.new
|
752
|
+
config[:description] = "" unless config[:description]
|
753
|
+
config[:options] = [] unless config[:options]
|
754
|
+
config[:params] = [] unless config[:params]
|
755
|
+
config[:alias] = "" unless config[:alias]
|
756
|
+
self.new config
|
757
|
+
end
|
758
|
+
|
759
|
+
def self.actions
|
760
|
+
return @@actions
|
761
|
+
end
|
762
|
+
|
763
|
+
end
|
764
|
+
module CommandLine extend self
|
765
|
+
Action = CommandAction
|
766
|
+
end
|
767
|
+
|
768
|
+
module CommandFile extend self
|
769
|
+
Specification = self
|
770
|
+
def new; @spec = {};
|
771
|
+
yield(self)
|
772
|
+
print "\n"
|
773
|
+
print @spec
|
774
|
+
exit
|
775
|
+
end
|
776
|
+
|
777
|
+
def parameters params; @spec['parameters'] = params; end
|
778
|
+
def alias params; @spec['alias'] = params; end
|
779
|
+
def description params; @spec['description'] = params ; end
|
780
|
+
def option params; @spec['options'] = [] unless @spec['options']; @spec['options'] << params ; end
|
781
|
+
def name params; @spec['name'] = params; end
|
782
|
+
def execute params; @spec['execute'] = params; end
|
783
|
+
def exclude params; @spec['exclude'] = params; end
|
784
|
+
end
|
785
|
+
|
786
|
+
class String #extend String class in order to add coloration capabilitise
|
787
|
+
# colorization
|
788
|
+
def colorize(color_code)
|
789
|
+
"\e[#{color_code}m#{self}\e[0m"
|
790
|
+
end
|
791
|
+
|
792
|
+
def bold
|
793
|
+
colorize(1)
|
794
|
+
end
|
795
|
+
|
796
|
+
def color colorName
|
797
|
+
colorize(1) #make it bold first
|
798
|
+
case colorName
|
799
|
+
when "red"
|
800
|
+
colorize(1).colorize(31)
|
801
|
+
when "blue"
|
802
|
+
colorize(1).colorize(34)
|
803
|
+
when "yellow"
|
804
|
+
colorize(1).colorize(33)
|
805
|
+
when "green"
|
806
|
+
colorize(1).colorize(32)
|
807
|
+
when "cyan"
|
808
|
+
colorize(1).colorize(36)
|
809
|
+
when "pink"
|
810
|
+
colorize(1).colorize(35)
|
811
|
+
when "white"
|
812
|
+
colorize(1).colorize(37)
|
813
|
+
when "normal"
|
814
|
+
colorize(0)
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
def is_namespace?
|
819
|
+
if self.split(':').length >= 2
|
820
|
+
return true
|
821
|
+
else
|
822
|
+
return false
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
end
|
827
|
+
|
828
|
+
class ActionAction < CommandLine::Action; register description: "create or overwrite an action",
|
829
|
+
alias: 'a',
|
830
|
+
params: ['name']
|
831
|
+
|
832
|
+
|
833
|
+
def action inputs, arguments, options, namespaces
|
834
|
+
if arguments['name']
|
835
|
+
createAction arguments['name'],namespaces if validate arguments['name']
|
836
|
+
else
|
837
|
+
error
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
def error
|
842
|
+
puts 'a valid name has to be provided in order to create the action'
|
843
|
+
puts 'use "cli action <name>"'
|
844
|
+
end
|
845
|
+
|
846
|
+
def validate name
|
847
|
+
if name.length == 0 || name[/[a-zA-Z]+/] != name
|
848
|
+
error
|
849
|
+
return false
|
850
|
+
else
|
851
|
+
return true
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
def register namespaces
|
856
|
+
print namespaces
|
857
|
+
return " register"
|
858
|
+
end
|
859
|
+
|
860
|
+
def createAction name,namespaces
|
861
|
+
puts "creating #{name} action"
|
862
|
+
lines = [
|
863
|
+
"class #{name.capitalize}Action < CommandLine::Action",
|
864
|
+
" register",
|
865
|
+
" def action inputs, arguments, options, namespaces",
|
866
|
+
" ",
|
867
|
+
" end",
|
868
|
+
"end",
|
869
|
+
]
|
870
|
+
|
871
|
+
File.open("lib/actions/#{name}.rb", 'a') do |f|
|
872
|
+
lines.each do |line|
|
873
|
+
f.print line, "\n"
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
|
881
|
+
|
882
|
+
|
883
|
+
class BuildAction < CommandLine::Action
|
884
|
+
register description: "build the cli"
|
885
|
+
|
886
|
+
|
887
|
+
# description: "this the description of build action"
|
888
|
+
# params: ["port"]
|
889
|
+
# options: [['-d -deploy','deploy'], ['-i','international']]
|
890
|
+
|
891
|
+
def action inputs, arguments, options, namespaces
|
892
|
+
puts "packing the cli .."
|
893
|
+
CommandLineInterface.terminal "rake install"
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
|
898
|
+
|
899
|
+
|
900
|
+
class ErrorAction < CommandLine::Action
|
901
|
+
register
|
902
|
+
|
903
|
+
def action inputs, arguments, options, namespaces
|
904
|
+
puts "\"#{inputs[0].show if inputs[0]}\" is not a valid #{CommandLineConfig::Name} Command"
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
|
909
|
+
|
910
|
+
|
911
|
+
class HelpAction < CommandLine::Action
|
912
|
+
register
|
913
|
+
|
914
|
+
|
915
|
+
def action inputs, arguments, options, namespaces
|
916
|
+
self.help
|
917
|
+
end
|
918
|
+
|
919
|
+
def help
|
920
|
+
rows = scanConfig # [{name:" ", "params":"", "description":"saljdn"} ]
|
921
|
+
puts "\n #{CommandLineConfig::Name} - #{CommandLineConfig::Version}"
|
922
|
+
table style: { :border_x => "", :border_y => "", :border_i => "" } do |row|
|
923
|
+
rows.each do |c|
|
924
|
+
name = c[:name]; next if name == 'help' || name == "version" || name == "error" || name.nil?
|
925
|
+
row.add [name.color(CommandLineConfig::Color) ,c[:params] ,c[:description]]
|
926
|
+
end
|
927
|
+
# row.separation
|
928
|
+
# rows.each do |c|
|
929
|
+
# name = c[:name]; if name == 'help' || name == "version"
|
930
|
+
# row.add [name ,c[:params] ,c[:description]]
|
931
|
+
# end
|
932
|
+
# end
|
933
|
+
|
934
|
+
end
|
935
|
+
print "\n"
|
936
|
+
#scanning for commandfiles in order to take configuration of each commandString
|
937
|
+
#scanning all commands to get thei config
|
938
|
+
#output a table
|
939
|
+
end
|
940
|
+
|
941
|
+
def scanConfig
|
942
|
+
rows = []
|
943
|
+
# cmd.configuration.call() #make config anvailable
|
944
|
+
CommandAction.actions.each do |cmd|
|
945
|
+
params = ""
|
946
|
+
cmd.args.each do |p|
|
947
|
+
params += "<#{p}> "
|
948
|
+
end
|
949
|
+
|
950
|
+
name = cmd.class.to_s.downcase.split('action')[0]
|
951
|
+
rows << { name: name , params: params, description: cmd.description }
|
952
|
+
end
|
953
|
+
return rows
|
954
|
+
end
|
955
|
+
|
956
|
+
def table params
|
957
|
+
|
958
|
+
table = Terminal::Table.new params do |row|
|
959
|
+
def row.add params; self.add_row params end
|
960
|
+
def row.separation; self.add_separator; end
|
961
|
+
yield(row) if block_given?
|
962
|
+
end
|
963
|
+
puts table
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
class NewAction < CommandLine::Action
|
968
|
+
register description: 'create a new cli',
|
969
|
+
params: ['name', 'color']
|
970
|
+
# options: [['-g --general', 'general']]
|
971
|
+
|
972
|
+
|
973
|
+
|
974
|
+
def action inputs, arguments, options, namespaces
|
975
|
+
unless arguments['name']
|
976
|
+
puts "A name for the cli has to be provided"
|
977
|
+
puts " cli new ... <- replace '...' with the name of your cli"
|
978
|
+
puts "Don't worry, you can change the name of your cli very easily after if you want so"
|
979
|
+
exit
|
980
|
+
end
|
981
|
+
msg = CommandLineInterface.terminal "git clone -b blank https://github.com/iliasbhal/cli-framework #{arguments['name']}"
|
982
|
+
if msg
|
983
|
+
# modify the configuration file so it matches the name
|
984
|
+
File.open("#{arguments['name']}/lib/configuration.rb", 'a') { |d| ;
|
985
|
+
d.truncate(0) # erase all the content of a file
|
986
|
+
d.print " module CommandLineConfig", "\n"
|
987
|
+
d.print " Name ||= '#{arguments['name']}'","\n"
|
988
|
+
d.print " Color ||= 'blue'", "\n"
|
989
|
+
d.print " Version ||= '0.0.0'","\n"
|
990
|
+
d.print " Else ||= help","\n"
|
991
|
+
d.print " Special ||= [['-v','version'], ['-help','help']]","\n"
|
992
|
+
d.print "end"
|
993
|
+
}
|
994
|
+
# enter the folder and modify git
|
995
|
+
puts "initilializing git ..."
|
996
|
+
Dir.chdir("#{arguments['name']}") do |path|
|
997
|
+
`git remote remove origin`
|
998
|
+
`git branch -m blank master`
|
999
|
+
`git add . && git commit -m "first commit"`
|
1000
|
+
`git reset && git init`
|
1001
|
+
end
|
1002
|
+
puts "done !"
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
|
1008
|
+
class ServerAction < CommandLine::Action
|
1009
|
+
register description: "launch a server localy with power :)",
|
1010
|
+
params: ["port"],
|
1011
|
+
options: [['-d -deploy','deploy'], ['-i','international']]
|
1012
|
+
|
1013
|
+
|
1014
|
+
def action inputs, arguments, options, namespaces
|
1015
|
+
Input.ask question: "what is your name ?",
|
1016
|
+
dialogue: "[?] Serverless: ",
|
1017
|
+
default: 'john'
|
1018
|
+
|
1019
|
+
Input.ask question: "what is your name ?",
|
1020
|
+
dialogue: "[?] Serverless: ",
|
1021
|
+
type: 'password', # can be confirm / password or just nothing
|
1022
|
+
default: 'john'
|
1023
|
+
|
1024
|
+
Input.ask question: "what is your name ?",
|
1025
|
+
dialogue: "[?] Serverless: ",
|
1026
|
+
type: 'confirm', # YyTt FfNn
|
1027
|
+
default: 'Y/N'
|
1028
|
+
|
1029
|
+
Choice.ask question: 'select from below:', default: 0,
|
1030
|
+
dialogue: "[?] Serverless: ",
|
1031
|
+
multiple: true,
|
1032
|
+
choices: [
|
1033
|
+
{ name: 'choice one', description: " # not a good choice :p",as: 'jehe'},
|
1034
|
+
{ name: 'choice two'},
|
1035
|
+
{ name: 'choice one', description: " # not a good choice :p"},
|
1036
|
+
{ name: 'choice two'},
|
1037
|
+
{ name: 'choice one', description: " # not a good choice :p"},
|
1038
|
+
{ name: 'choice two'},
|
1039
|
+
{ name: 'choice one', description: " # not a good choice :p"},
|
1040
|
+
{ name: 'choice two'}
|
1041
|
+
]
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
class VersionAction < CommandLine::Action
|
1046
|
+
register description: "return the version on the cli"
|
1047
|
+
|
1048
|
+
|
1049
|
+
def action inputs, arguments, options, namespaces
|
1050
|
+
print inputs, "\n"
|
1051
|
+
print arguments, "\n"
|
1052
|
+
print options, "\n"
|
1053
|
+
print namespaces, "\n"
|
1054
|
+
puts CommandLineConfig::Version
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
|
1059
|
+
CommandLineInterface.new
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cli-framework
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ilias Bhallil
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: terminal-table
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Framework for quickly building a Command line interface
|
28
|
+
email:
|
29
|
+
- ilias.bhal@gmail.com
|
30
|
+
executables:
|
31
|
+
- cli
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- bin/cli
|
36
|
+
homepage: http://iliasbhallil.github.com
|
37
|
+
licenses: []
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.5.1
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: ''
|
59
|
+
test_files: []
|