pi 0.1.23 → 0.1.24
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/README +367 -404
- data/lib/cli.rb +1 -0
- data/lib/cli/choose_helper.rb +123 -25
- data/lib/cli/commands/apps.rb +241 -303
- data/lib/cli/commands/dns.rb +38 -73
- data/lib/cli/commands/projects.rb +179 -160
- data/lib/cli/commands/services.rb +55 -80
- data/lib/cli/commands/user.rb +26 -10
- data/lib/cli/interact_helper.rb +538 -0
- data/lib/cli/runner.rb +77 -53
- data/lib/cli/usage.rb +55 -39
- data/lib/cli/version.rb +1 -1
- data/lib/pi/client.rb +92 -77
- data/lib/pi/const.rb +2 -0
- metadata +5 -5
- data/lib/cli/commands/admin.rb +0 -19
@@ -1,37 +1,12 @@
|
|
1
|
-
require "interact"
|
2
1
|
module PI::Cli::Command
|
3
2
|
class Services < Base
|
4
3
|
include PI::Cli::ChooseHelper
|
5
|
-
include
|
4
|
+
include PI::Cli::InteractHelper
|
6
5
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
useapp = choose_app(projectid)
|
12
|
-
appname = useapp[:name]
|
13
|
-
end
|
14
|
-
services = client.usable_services(appname)
|
15
|
-
return display JSON.pretty_generate(services) if @options[:json]
|
16
|
-
return display "No Services" if services.nil? || services.empty?
|
17
|
-
services.sort! {|a, b| a[:name] <=> b[:name] }
|
18
|
-
services_table = table do |t|
|
19
|
-
t.headings = 'Name', 'Version', 'Type'
|
20
|
-
services.each do |s|
|
21
|
-
t << [s[:name], s[:version] ,s[:service_type]]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
display services_table
|
25
|
-
end
|
26
|
-
|
27
|
-
def app_service(appname=nil)
|
28
|
-
unless appname
|
29
|
-
useproject = choose_project
|
30
|
-
projectid = useproject[:id]
|
31
|
-
useapp = choose_app(projectid)
|
32
|
-
appname = useapp[:name]
|
33
|
-
end
|
34
|
-
services = client.app_service(appname)
|
6
|
+
def app_service(appid_or_appname=nil)
|
7
|
+
client.check_login_status
|
8
|
+
app = choose_app_help_target_not_all(appid_or_appname)
|
9
|
+
services = client.app_service(app[:id])
|
35
10
|
return display JSON.pretty_generate(services) if @options[:json]
|
36
11
|
return display "No Services" if services.nil? || services.empty?
|
37
12
|
services.sort! {|a, b| a[:name] <=> b[:name] }
|
@@ -44,17 +19,10 @@ module PI::Cli::Command
|
|
44
19
|
display services_table
|
45
20
|
end
|
46
21
|
|
47
|
-
def bind_service(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
useapp = choose_app(projectid)
|
52
|
-
appname = useapp[:name]
|
53
|
-
end
|
54
|
-
app = client.app_get_byname(appname)
|
55
|
-
err "The application is not found! App name :#{appname}" unless (appname == app[:name] ? true : false)
|
56
|
-
|
57
|
-
services = client.usable_services(appname)
|
22
|
+
def bind_service(appid_or_appname=nil)
|
23
|
+
client.check_login_status
|
24
|
+
app = choose_app_help_target_not_all(appid_or_appname)
|
25
|
+
services = client.usable_services(app[:id])
|
58
26
|
err "No usable services!" if services.nil? || services.empty?
|
59
27
|
|
60
28
|
choices = Array.new
|
@@ -62,10 +30,12 @@ module PI::Cli::Command
|
|
62
30
|
choices << s[:name]
|
63
31
|
end
|
64
32
|
|
65
|
-
manifest =
|
66
|
-
manifest = manifest.to_a
|
33
|
+
manifest = asks "Select services, indexed list?", :choices => choices, :indexed => true
|
67
34
|
display "Selected services: ",false
|
68
|
-
|
35
|
+
manifest.each do |m|
|
36
|
+
display "#{m} ",false
|
37
|
+
end
|
38
|
+
display "\n"
|
69
39
|
display "Binding service: ",false
|
70
40
|
|
71
41
|
t = Thread.new do
|
@@ -76,55 +46,60 @@ module PI::Cli::Command
|
|
76
46
|
end
|
77
47
|
end
|
78
48
|
|
79
|
-
client.bind_service(
|
80
|
-
result = check_status(app[:name], "bindservice")
|
49
|
+
client.bind_service(app[:id],manifest)
|
50
|
+
result = check_status(app[:targetName], app[:name], "bindservice")
|
81
51
|
Thread.kill(t)
|
82
|
-
if result[:code] == 200
|
83
|
-
|
52
|
+
if result[:code] == 200
|
53
|
+
if not result[:text].empty?
|
54
|
+
display "OK".green
|
55
|
+
else
|
56
|
+
display "Please try again"
|
57
|
+
end
|
84
58
|
else
|
85
59
|
err result[:text]
|
86
60
|
end
|
87
61
|
end
|
88
62
|
|
89
|
-
def unbind_service(
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
services =
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
display "Unbinding service: ",false
|
114
|
-
|
63
|
+
def unbind_service(appid_or_appname=nil)
|
64
|
+
client.check_login_status
|
65
|
+
app = choose_app_help_target_not_all(appid_or_appname)
|
66
|
+
services = client.app_service(app[:id])
|
67
|
+
err "No binded services!" if services.nil? || services.empty?
|
68
|
+
service_choices = Array.new
|
69
|
+
services.each do |s|
|
70
|
+
service_choices << s[:name]
|
71
|
+
end
|
72
|
+
services = asks "Select Service", :choices => service_choices, :indexed => true
|
73
|
+
display "Selected DNS: ",false
|
74
|
+
services.each do |m|
|
75
|
+
display "#{m} ",false
|
76
|
+
end
|
77
|
+
display "\n"
|
78
|
+
services.each do |service|
|
79
|
+
do_unbind_service(app, service)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def do_unbind_service(app, service)
|
86
|
+
display "Unbinding service '#{service}': ",false
|
115
87
|
t = Thread.new do
|
116
88
|
loop do
|
117
89
|
display '.', false
|
118
90
|
sleep (1)
|
119
91
|
break unless t.alive?
|
120
92
|
end
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
result = check_status(app[:name], "unbindservice")
|
93
|
+
end
|
94
|
+
client.unbind_service(app[:id], service)
|
95
|
+
result = check_status(app[:targetName], app[:name], "unbindservice")
|
125
96
|
Thread.kill(t)
|
126
|
-
if result[:code] == 200
|
127
|
-
|
97
|
+
if result[:code] == 200
|
98
|
+
if not result[:text].empty?
|
99
|
+
display "OK".green
|
100
|
+
else
|
101
|
+
display "Please try again"
|
102
|
+
end
|
128
103
|
else
|
129
104
|
err result[:text]
|
130
105
|
end
|
data/lib/cli/commands/user.rb
CHANGED
@@ -3,8 +3,12 @@ require 'set'
|
|
3
3
|
module PI::Cli::Command
|
4
4
|
|
5
5
|
class User < Base
|
6
|
-
|
6
|
+
YES_SET = Set.new(["y", "Y", "yes", "YES"])
|
7
|
+
NO_SET = Set.new(["n", "N", "no", "NO"])
|
8
|
+
|
7
9
|
def login(url=nil)
|
10
|
+
user = @options[:user]
|
11
|
+
password = @options[:password]
|
8
12
|
unless url
|
9
13
|
url = ask "Attempting login to '#{target_url}'?", :default => true
|
10
14
|
if url == true
|
@@ -14,12 +18,16 @@ module PI::Cli::Command
|
|
14
18
|
end
|
15
19
|
end
|
16
20
|
url = "#{target_url}" if url.nil? || url.empty?
|
17
|
-
eval("PI::Cli::Command::Misc").new().send("set_target", url)
|
21
|
+
eval("PI::Cli::Command::Misc").new().send("set_target", url)
|
22
|
+
unless client.target_valid?
|
23
|
+
display "Host is not available or is not valid: '#{target_url}'".red
|
24
|
+
display "\n<<<\n#{client.raw_info}\n>>>\n"
|
25
|
+
exit(false)
|
26
|
+
end
|
27
|
+
|
18
28
|
tries ||= 0
|
19
|
-
user = ask "User"
|
20
|
-
password = ask "Password", :echo => '*'
|
21
|
-
err "Need a valid user" unless user
|
22
|
-
err "Need a password" unless password
|
29
|
+
user = ask "User" unless user
|
30
|
+
password = ask "Password", :echo => '*' unless password
|
23
31
|
login_and_save_token(user, password)
|
24
32
|
say "Successfully logged into [#{target_url}]".green
|
25
33
|
rescue PI::Client::TargetError
|
@@ -27,7 +35,7 @@ module PI::Cli::Command
|
|
27
35
|
retry if (tries += 1) < 3 && !@options[:passwd]
|
28
36
|
exit 1
|
29
37
|
rescue => e
|
30
|
-
display "Problem with login, #{e}, try again
|
38
|
+
display "Problem with login, #{e}, try again.".red
|
31
39
|
exit 1
|
32
40
|
end
|
33
41
|
|
@@ -46,6 +54,7 @@ module PI::Cli::Command
|
|
46
54
|
end
|
47
55
|
|
48
56
|
def user
|
57
|
+
client.check_login_status
|
49
58
|
user = client.user_info
|
50
59
|
github_info = client.github_info
|
51
60
|
github_info[:name] = "null" if github_info[:name].empty? || github_info[:name].nil?
|
@@ -62,6 +71,7 @@ module PI::Cli::Command
|
|
62
71
|
end
|
63
72
|
|
64
73
|
def targets
|
74
|
+
client.check_login_status
|
65
75
|
targets = client.targets
|
66
76
|
return display JSON.pretty_generate(targets) if @options[:json]
|
67
77
|
return display 'No Targets' if targets.empty?
|
@@ -75,7 +85,8 @@ module PI::Cli::Command
|
|
75
85
|
display targets_table
|
76
86
|
end
|
77
87
|
|
78
|
-
def password(newpassword=nil)
|
88
|
+
def password(newpassword=nil)
|
89
|
+
client.check_login_status
|
79
90
|
unless newpassword
|
80
91
|
loop{
|
81
92
|
newpassword = ask "New Password", :echo => '*'
|
@@ -94,9 +105,12 @@ module PI::Cli::Command
|
|
94
105
|
end
|
95
106
|
|
96
107
|
def github(name=nil)
|
108
|
+
client.check_login_status
|
109
|
+
email = @options[:email]
|
110
|
+
password = @options[:password]
|
97
111
|
name = ask "Please input your name" unless name
|
98
|
-
email = ask "Please input your email"
|
99
|
-
password = ask "Please input your password", :echo => '*'
|
112
|
+
email = ask "Please input your email" unless email
|
113
|
+
password = ask "Please input your password", :echo => '*' unless password
|
100
114
|
manifest = {
|
101
115
|
:name => "#{name}",
|
102
116
|
:password => "#{password}",
|
@@ -108,6 +122,7 @@ module PI::Cli::Command
|
|
108
122
|
end
|
109
123
|
|
110
124
|
def runtimes
|
125
|
+
client.check_login_status
|
111
126
|
runtimes_info = client.runtimes
|
112
127
|
return display JSON.pretty_generate(runtimes_info) if @options[:json]
|
113
128
|
return display "No Runtimes" if runtimes_info.empty?
|
@@ -120,6 +135,7 @@ module PI::Cli::Command
|
|
120
135
|
end
|
121
136
|
|
122
137
|
def frameworks
|
138
|
+
client.check_login_status
|
123
139
|
runtimes = client.runtimes
|
124
140
|
java_runtime = runtimes[0].downcase
|
125
141
|
ruby_runtime = runtimes[1].downcase
|
@@ -0,0 +1,538 @@
|
|
1
|
+
module PI::Cli
|
2
|
+
module InteractHelper
|
3
|
+
EVENTS = {
|
4
|
+
"\b" => :backspace,
|
5
|
+
"\t" => :tab,
|
6
|
+
"\x01" => :home,
|
7
|
+
"\x03" => :interrupt,
|
8
|
+
"\x04" => :eof,
|
9
|
+
"\x05" => :end,
|
10
|
+
"\x17" => :kill_word,
|
11
|
+
"\x7f" => :backspace,
|
12
|
+
"\r" => :enter,
|
13
|
+
"\n" => :enter
|
14
|
+
}
|
15
|
+
|
16
|
+
ESCAPES = {
|
17
|
+
"[A" => :up, "H" => :up,
|
18
|
+
"[B" => :down, "P" => :down,
|
19
|
+
"[C" => :right, "M" => :right,
|
20
|
+
"[D" => :left, "K" => :left,
|
21
|
+
"[3~" => :delete, "S" => :delete,
|
22
|
+
"[H" => :home, "G" => :home,
|
23
|
+
"[F" => :end, "O" => :end,
|
24
|
+
"[Z" => :shift_tab
|
25
|
+
}
|
26
|
+
|
27
|
+
class InputState
|
28
|
+
attr_accessor :options, :answer, :position
|
29
|
+
|
30
|
+
def initialize(options = {}, answer = "", position = 0)
|
31
|
+
@options = options
|
32
|
+
@answer = answer
|
33
|
+
@position = position
|
34
|
+
@done = false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Call to signal to the input reader that it can stop.
|
38
|
+
def done!
|
39
|
+
@done = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Is the input finished/complete?
|
43
|
+
def done?
|
44
|
+
@done
|
45
|
+
end
|
46
|
+
|
47
|
+
def censor(what)
|
48
|
+
if with = @options[:echo]
|
49
|
+
with * what.size
|
50
|
+
else
|
51
|
+
what
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def display(what)
|
56
|
+
print(censor(what))
|
57
|
+
@position += what.size
|
58
|
+
end
|
59
|
+
|
60
|
+
def back(x)
|
61
|
+
return if x == 0
|
62
|
+
|
63
|
+
print("\b" * (x * char_size))
|
64
|
+
|
65
|
+
@position -= x
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear(x)
|
69
|
+
return if x == 0
|
70
|
+
|
71
|
+
print(" " * (x * char_size))
|
72
|
+
|
73
|
+
@position += x
|
74
|
+
|
75
|
+
back(x)
|
76
|
+
end
|
77
|
+
|
78
|
+
def goto(pos)
|
79
|
+
return if pos == position
|
80
|
+
|
81
|
+
if pos > position
|
82
|
+
display(answer[position .. pos])
|
83
|
+
else
|
84
|
+
print("\b" * (position - pos) * char_size)
|
85
|
+
end
|
86
|
+
|
87
|
+
@position = pos
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def char_size
|
93
|
+
@options[:echo] ? @options[:echo].size : 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def read_char(options = {})
|
98
|
+
input = options[:input] || $stdin
|
99
|
+
|
100
|
+
with_char_io(input) do
|
101
|
+
get_character(input)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def read_event(options = {})
|
106
|
+
input = options[:input] || $stdin
|
107
|
+
|
108
|
+
with_char_io(input) do
|
109
|
+
get_event(input)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_line(options = {})
|
114
|
+
input = options[:input] || $stdin
|
115
|
+
|
116
|
+
state = input_state(options)
|
117
|
+
with_char_io(input) do
|
118
|
+
until state.done?
|
119
|
+
handler(get_event(input), state)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
state.answer
|
124
|
+
end
|
125
|
+
|
126
|
+
def asks(question, options = {})
|
127
|
+
choices = options[:choices] && options[:choices].to_a
|
128
|
+
|
129
|
+
list_choices(choices, options) if choices
|
130
|
+
|
131
|
+
while true
|
132
|
+
prompt(question, options)
|
133
|
+
ok, res = new_answered(read_line(options), options)
|
134
|
+
return res if ok
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def clear_input(state)
|
141
|
+
state.goto(0)
|
142
|
+
state.clear(state.answer.size)
|
143
|
+
state.answer = ""
|
144
|
+
end
|
145
|
+
|
146
|
+
def set_input(state, input)
|
147
|
+
clear_input(state)
|
148
|
+
state.display(input)
|
149
|
+
state.answer = input
|
150
|
+
end
|
151
|
+
|
152
|
+
def redraw_input(state)
|
153
|
+
pos = state.position
|
154
|
+
state.goto(0)
|
155
|
+
state.display(state.answer)
|
156
|
+
state.goto(pos)
|
157
|
+
end
|
158
|
+
|
159
|
+
def input_state(options)
|
160
|
+
InputState.new(options)
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_event(input)
|
164
|
+
escaped = false
|
165
|
+
escape_seq = ""
|
166
|
+
|
167
|
+
while true
|
168
|
+
c = get_character(input)
|
169
|
+
|
170
|
+
if not c
|
171
|
+
return :eof
|
172
|
+
elsif c == "\e" || c == "\xE0"
|
173
|
+
escaped = true
|
174
|
+
elsif escaped
|
175
|
+
escape_seq << c
|
176
|
+
|
177
|
+
if cmd = ESCAPES[escape_seq]
|
178
|
+
return cmd
|
179
|
+
elsif ESCAPES.select { |k, v|
|
180
|
+
k.start_with? escape_seq
|
181
|
+
}.empty?
|
182
|
+
escaped, escape_seq = false, ""
|
183
|
+
end
|
184
|
+
elsif EVENTS.key? c
|
185
|
+
return EVENTS[c]
|
186
|
+
elsif c < " "
|
187
|
+
# ignore
|
188
|
+
else
|
189
|
+
return [:key, c]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def new_answered(ans, options)
|
195
|
+
print "\n"
|
196
|
+
|
197
|
+
if ans.empty?
|
198
|
+
if options.key?(:default)
|
199
|
+
[true, options[:default]]
|
200
|
+
end
|
201
|
+
elsif choices = options[:choices]
|
202
|
+
matches = choices.select { |x|
|
203
|
+
choice_completion(x, options).start_with? ans
|
204
|
+
}
|
205
|
+
|
206
|
+
if choices and ans =~ /^\s*\d+\s*$/ and \
|
207
|
+
ans.to_i - 1 >= 0 and res = choices.to_a[ans.to_i - 1]
|
208
|
+
res = Array.new
|
209
|
+
res = res.push(choices.to_a[ans.to_i - 1])
|
210
|
+
[true, res]
|
211
|
+
#add
|
212
|
+
elsif choices and ans =~ /^\s*(\d+,)*\d+\s*$/ and \
|
213
|
+
ans.to_i - 1 >= 0
|
214
|
+
ans = ans.split(",")
|
215
|
+
res = Array.new
|
216
|
+
ans.each do |a|
|
217
|
+
res.push(choices[a.to_i - 1])
|
218
|
+
end
|
219
|
+
# res = ["rails.samsungpaas.comrailstest.samsungpaas.com"]
|
220
|
+
[true, res]
|
221
|
+
#add
|
222
|
+
elsif matches.size == 1
|
223
|
+
[true, matches.first]
|
224
|
+
elsif matches.size > 1
|
225
|
+
matches_list = matches.collect { |m|
|
226
|
+
show_choice(m, options)
|
227
|
+
}.join " or "
|
228
|
+
|
229
|
+
puts "Please disambiguate: #{matches_list}?"
|
230
|
+
|
231
|
+
[false, nil]
|
232
|
+
else
|
233
|
+
puts "Unknown answer, please try again!"
|
234
|
+
[false, nil]
|
235
|
+
end
|
236
|
+
else
|
237
|
+
[true, match_type(ans, options[:default])]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def list_choices(choices, options = {})
|
242
|
+
return unless options[:indexed]
|
243
|
+
|
244
|
+
choices.each_with_index do |o, i|
|
245
|
+
puts "#{i + 1}: #{show_choice(o, options)}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def show_choice(choice, options = {})
|
250
|
+
display = options[:display] || proc(&:to_s)
|
251
|
+
display.call(choice)
|
252
|
+
end
|
253
|
+
|
254
|
+
def choice_completion(choice, options = {})
|
255
|
+
complete = options[:complete] || options[:display] || proc(&:to_s)
|
256
|
+
complete.call(choice)
|
257
|
+
end
|
258
|
+
|
259
|
+
def common_prefix(*strs)
|
260
|
+
return strs.first.dup if strs.size == 1
|
261
|
+
|
262
|
+
longest = strs.sort_by(&:size).last
|
263
|
+
longest.size.times do |i|
|
264
|
+
sub = longest[0..(-1 - i)]
|
265
|
+
if strs.all? { |s| s.start_with?(sub) }
|
266
|
+
return sub
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
""
|
271
|
+
end
|
272
|
+
|
273
|
+
def handler(which, state)
|
274
|
+
ans = state.answer
|
275
|
+
pos = state.position
|
276
|
+
|
277
|
+
case which
|
278
|
+
when :up
|
279
|
+
# nothing
|
280
|
+
|
281
|
+
when :down
|
282
|
+
# nothing
|
283
|
+
|
284
|
+
when :tab
|
285
|
+
matches =
|
286
|
+
if choices = state.options[:choices]
|
287
|
+
choices.collect { |c|
|
288
|
+
choice_completion(c, state.options)
|
289
|
+
}.select { |c|
|
290
|
+
c.start_with? ans
|
291
|
+
}
|
292
|
+
else
|
293
|
+
matching_paths(ans)
|
294
|
+
end
|
295
|
+
|
296
|
+
if matches.empty?
|
297
|
+
print("\a") # bell
|
298
|
+
else
|
299
|
+
old = ans
|
300
|
+
ans = state.answer = common_prefix(*matches)
|
301
|
+
state.display(ans[pos .. -1])
|
302
|
+
print("\a") if ans == old
|
303
|
+
end
|
304
|
+
|
305
|
+
when :right
|
306
|
+
unless pos == ans.size
|
307
|
+
state.display(ans[pos .. pos])
|
308
|
+
end
|
309
|
+
|
310
|
+
when :left
|
311
|
+
unless pos == 0
|
312
|
+
state.back(1)
|
313
|
+
end
|
314
|
+
|
315
|
+
when :delete
|
316
|
+
unless pos == ans.size
|
317
|
+
ans.slice!(pos, 1)
|
318
|
+
rest = ans[pos .. -1]
|
319
|
+
state.display(rest)
|
320
|
+
state.clear(1)
|
321
|
+
state.back(rest.size)
|
322
|
+
end
|
323
|
+
|
324
|
+
when :home
|
325
|
+
state.goto(0)
|
326
|
+
|
327
|
+
when :end
|
328
|
+
state.goto(ans.size)
|
329
|
+
|
330
|
+
when :backspace
|
331
|
+
if pos > 0
|
332
|
+
rest = ans[pos .. -1]
|
333
|
+
|
334
|
+
ans.slice!(pos - 1, 1)
|
335
|
+
|
336
|
+
state.back(1)
|
337
|
+
state.display(rest)
|
338
|
+
state.clear(1)
|
339
|
+
state.back(rest.size)
|
340
|
+
end
|
341
|
+
|
342
|
+
when :interrupt
|
343
|
+
raise Interrupt.new
|
344
|
+
|
345
|
+
when :eof
|
346
|
+
state.done! if ans.empty?
|
347
|
+
|
348
|
+
when :kill_word
|
349
|
+
if pos > 0
|
350
|
+
start = /[[:alnum:]]*\s*[^[:alnum:]]?$/ =~ ans[0 .. (pos - 1)]
|
351
|
+
|
352
|
+
if pos < ans.size
|
353
|
+
to_end = ans.size - pos
|
354
|
+
rest = ans[pos .. -1]
|
355
|
+
state.clear(to_end)
|
356
|
+
end
|
357
|
+
|
358
|
+
length = pos - start
|
359
|
+
|
360
|
+
ans.slice!(start, length)
|
361
|
+
state.back(length)
|
362
|
+
state.clear(length)
|
363
|
+
|
364
|
+
if to_end
|
365
|
+
state.display(rest)
|
366
|
+
state.back(to_end)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
when :enter
|
371
|
+
state.done!
|
372
|
+
|
373
|
+
when Array
|
374
|
+
case which[0]
|
375
|
+
when :key
|
376
|
+
c = which[1]
|
377
|
+
rest = ans[pos .. -1]
|
378
|
+
|
379
|
+
ans.insert(pos, c)
|
380
|
+
|
381
|
+
state.display(c + rest)
|
382
|
+
state.back(rest.size)
|
383
|
+
end
|
384
|
+
|
385
|
+
else
|
386
|
+
return false
|
387
|
+
end
|
388
|
+
|
389
|
+
true
|
390
|
+
end
|
391
|
+
|
392
|
+
def matching_paths(input)
|
393
|
+
home = File.expand_path("~")
|
394
|
+
|
395
|
+
Dir.glob(input.sub("~", home) + "*").collect do |p|
|
396
|
+
p.sub(home, "~")
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def prompt(question, options = {})
|
401
|
+
print question
|
402
|
+
|
403
|
+
if (choices = options[:choices]) && !options[:indexed]
|
404
|
+
print " (#{choices.collect(&:to_s).join ", "})"
|
405
|
+
end
|
406
|
+
|
407
|
+
case options[:default]
|
408
|
+
when true
|
409
|
+
print " [Yn]"
|
410
|
+
when false
|
411
|
+
print " [yN]"
|
412
|
+
when nil
|
413
|
+
else
|
414
|
+
print " [#{options[:default]}]"
|
415
|
+
end
|
416
|
+
|
417
|
+
print ": "
|
418
|
+
end
|
419
|
+
|
420
|
+
def match_type(str, x)
|
421
|
+
case x
|
422
|
+
when Integer
|
423
|
+
str.to_i
|
424
|
+
when true, false
|
425
|
+
str.upcase.start_with? "Y"
|
426
|
+
else
|
427
|
+
str
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def with_char_io(input)
|
432
|
+
before = set_input_state(input)
|
433
|
+
yield
|
434
|
+
ensure
|
435
|
+
restore_input_state(input, before)
|
436
|
+
end
|
437
|
+
|
438
|
+
def chr(x)
|
439
|
+
x && x.chr
|
440
|
+
end
|
441
|
+
|
442
|
+
private :chr
|
443
|
+
|
444
|
+
# Definitions for reading character-by-character with no echoing.
|
445
|
+
begin
|
446
|
+
require "Win32API"
|
447
|
+
|
448
|
+
def set_input_state(input)
|
449
|
+
nil
|
450
|
+
end
|
451
|
+
|
452
|
+
def restore_input_state(input, state)
|
453
|
+
nil
|
454
|
+
end
|
455
|
+
|
456
|
+
def get_character(input)
|
457
|
+
if input == STDIN
|
458
|
+
begin
|
459
|
+
chr(Win32API.new("msvcrt", "_getch", [], "L").call)
|
460
|
+
rescue
|
461
|
+
chr(Win32API.new("crtdll", "_getch", [], "L").call)
|
462
|
+
end
|
463
|
+
else
|
464
|
+
chr(input.getc)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
rescue LoadError
|
468
|
+
begin
|
469
|
+
require "termios"
|
470
|
+
|
471
|
+
def set_input_state(input)
|
472
|
+
return nil unless input.tty?
|
473
|
+
before = Termios.getattr(input)
|
474
|
+
|
475
|
+
new = before.dup
|
476
|
+
new.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
|
477
|
+
new.c_cc[Termios::VMIN] = 1
|
478
|
+
|
479
|
+
Termios.setattr(input, Termios::TCSANOW, new)
|
480
|
+
|
481
|
+
before
|
482
|
+
end
|
483
|
+
|
484
|
+
def restore_input_state(input, before)
|
485
|
+
if before
|
486
|
+
Termios.setattr(input, Termios::TCSANOW, before)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def get_character(input)
|
491
|
+
chr(input.getc)
|
492
|
+
end
|
493
|
+
rescue LoadError
|
494
|
+
begin
|
495
|
+
require "ffi-ncurses"
|
496
|
+
|
497
|
+
def set_input_state(input)
|
498
|
+
return nil unless input.tty?
|
499
|
+
|
500
|
+
FFI::NCurses.initscr
|
501
|
+
FFI::NCurses.cbreak
|
502
|
+
|
503
|
+
true
|
504
|
+
end
|
505
|
+
|
506
|
+
def restore_input_state(input, before)
|
507
|
+
if before
|
508
|
+
FFI::NCurses.endwin
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def get_character(input)
|
513
|
+
chr(input.getc)
|
514
|
+
end
|
515
|
+
rescue LoadError
|
516
|
+
def set_input_state(input)
|
517
|
+
return nil unless input.tty?
|
518
|
+
|
519
|
+
before = `stty -g`
|
520
|
+
|
521
|
+
Kernel.system("stty -echo -icanon isig")
|
522
|
+
|
523
|
+
before
|
524
|
+
end
|
525
|
+
|
526
|
+
def restore_input_state(input, before)
|
527
|
+
Kernel.system("stty #{before}") if before
|
528
|
+
end
|
529
|
+
|
530
|
+
def get_character(input)
|
531
|
+
chr(input.getc)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
end
|
538
|
+
end
|