cli-framework 0.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.
- 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: []
|