pi 0.1.23 → 0.1.24
Sign up to get free protection for your applications and to get access to all the features.
- 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
|