When interrupted, keep the content of the raw_input() buffer to
[gsh.git] / gsh / control_commands.py
blobc55d5bc908dd880be990c48f6b2ef1ea02c486e5
1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
19 import asyncore
20 import glob
21 import os
23 from gsh.control_commands_helpers import complete_shells, selected_shells
24 from gsh.control_commands_helpers import list_control_commands
25 from gsh.control_commands_helpers import get_control_command, toggle_shells
26 from gsh.control_commands_helpers import expand_local_path
27 from gsh.completion import complete_local_absolute_path
28 from gsh.console import console_output
29 from gsh import dispatchers
30 from gsh import remote_dispatcher
31 from gsh import stdin
32 from gsh import file_transfer
34 def complete_help(line, text):
35 colon = text.startswith(':')
36 text = text.lstrip(':')
37 res = [cmd + ' ' for cmd in list_control_commands() if \
38 cmd.startswith(text) and ' ' + cmd + ' ' not in line]
39 if colon:
40 res = [':' + cmd for cmd in res]
41 return res
43 def do_help(command):
44 """
45 Usage: :help [COMMAND]
46 List control commands or show their documentations.
47 """
48 command = command.strip()
49 if command:
50 texts = []
51 for name in command.split():
52 try:
53 cmd = get_control_command(name.lstrip(':'))
54 except AttributeError:
55 console_output('Unknown control command: %s\n' % name)
56 else:
57 doc = [d.strip() for d in cmd.__doc__.split('\n') if d.strip()]
58 texts.append('\n'.join(doc))
59 if texts:
60 console_output('\n\n'.join(texts))
61 console_output('\n')
62 else:
63 names = list_control_commands()
64 max_name_len = max(map(len, names))
65 for i in xrange(len(names)):
66 name = names[i]
67 txt = (max_name_len - len(name)) * ' ' + ':' + name + ' - '
68 doc = get_control_command(name).__doc__
69 txt += doc.split('\n')[2].strip() + '\n'
70 console_output(txt)
72 def complete_list(line, text):
73 return complete_shells(line, text)
75 def do_list(command):
76 """
77 Usage: :list [SHELLS...]
78 List remote shells and their states.
79 The special characters * ? and [] work as expected.
80 """
81 nr_active = nr_dead = 0
82 instances = []
83 for i in selected_shells(command):
84 instances.append(i.get_info())
85 if i.active:
86 nr_active += 1
87 else:
88 nr_dead += 1
89 dispatchers.format_info(instances)
90 console_output('%s\n\n%d active shells, %d dead shells, total: %d\n' % \
91 ('\n'.join(instances), nr_active, nr_dead, nr_active + nr_dead))
93 def do_quit(command):
94 """
95 Usage: :quit
96 Quit gsh.
97 """
98 raise asyncore.ExitNow(0)
100 def complete_chdir(line, text):
101 return [p + '/' for p in glob.glob(expand_local_path(text) + '*') if
102 os.path.isdir(p)]
104 def do_chdir(command):
106 Usage: :chdir PATH
107 Change the current directory of gsh (not the remote shells).
109 try:
110 os.chdir(expand_local_path(command))
111 except OSError, e:
112 console_output('%s\n' % str(e))
114 def complete_send_ctrl(line, text):
115 if len(line[:-1].split()) >= 2:
116 # Control letter already given in command line
117 return complete_shells(line, text, lambda i: i.enabled)
118 if text in ('c', 'd', 'z'):
119 return [text + ' ']
120 return ['c ', 'd ', 'z ']
122 def do_send_ctrl(command):
124 Usage: :send_ctrl LETTER [SHELLS...]
125 Send a control character to remote shells.
126 The first argument is the control character to send: c, d or z.
127 The remaining optional arguments are the destination shells.
128 The special characters * ? and [] work as expected.
130 split = command.split()
131 if not split:
132 console_output('Expected at least a letter\n')
133 return
134 letter = split[0]
135 if len(letter) != 1:
136 console_output('Expected a single letter, got: %s\n' % letter)
137 return
138 control_letter = chr(ord(letter.lower()) - ord('a') + 1)
139 for i in selected_shells(' '.join(split[1:])):
140 if i.enabled:
141 i.dispatch_write(control_letter)
143 def complete_reset_prompt(line, text):
144 return complete_shells(line, text, lambda i: i.enabled)
146 def do_reset_prompt(command):
148 Usage: :reset_prompt [SHELLS...]
149 Change the prompt to be recognized by gsh.
150 The special characters * ? and [] work as expected.
152 for i in selected_shells(command):
153 i.dispatch_command(i.init_string)
155 def complete_enable(line, text):
156 return complete_shells(line, text, lambda i: i.active and not i.enabled)
158 def do_enable(command):
160 Usage: :enable [SHELLS...]
161 Enable sending commands to remote shells.
162 The special characters * ? and [] work as expected.
164 toggle_shells(command, True)
166 def complete_disable(line, text):
167 return complete_shells(line, text, lambda i: i.enabled)
169 def do_disable(command):
171 Usage: :disable [SHELLS...]
172 Disable sending commands to remote shells.
173 The special characters * ? and [] work as expected.
175 toggle_shells(command, False)
177 def complete_reconnect(line, text):
178 return complete_shells(line, text, lambda i: not i.active)
180 def do_reconnect(command):
182 Usage: :reconnect [SHELLS...]
183 Try to reconnect to disconnected remote shells.
184 The special characters * ? and [] work as expected.
186 for i in selected_shells(command):
187 if not i.active:
188 i.reconnect()
190 def do_add(command):
192 Usage: :add NAMES...
193 Add one or many remote shells.
195 for host in command.split():
196 remote_dispatcher.remote_dispatcher(host)
198 def complete_purge(line, text):
199 return complete_shells(line, text, lambda i: not i.enabled)
201 def do_purge(command):
203 Usage: :purge [SHELLS...]
204 Delete disabled remote shells.
205 This helps to have a shorter list.
206 The special characters * ? and [] work as expected.
208 to_delete = []
209 for i in selected_shells(command):
210 if not i.enabled:
211 to_delete.append(i)
212 for i in to_delete:
213 i.disconnect()
214 i.close()
216 def do_rename(command):
218 Usage: :rename [NEW_NAME]
219 Rename all enabled remote shells with the argument.
220 The argument will be shell expanded on the remote processes. With no
221 argument, the original hostname will be restored as the displayed name.
223 for i in dispatchers.all_instances():
224 if i.enabled:
225 i.rename(command)
227 def do_hide_password(command):
229 Usage: :hide_password
230 Do not echo the next typed line.
231 This is useful when entering password. If debugging or logging is enabled,
232 it will be disabled to avoid displaying a password.
234 warned = False
235 for i in dispatchers.all_instances():
236 if i.enabled and i.debug:
237 i.debug = False
238 if not warned:
239 console_output('Debugging disabled to avoid displaying '
240 'passwords\n')
241 warned = True
242 stdin.set_echo(False)
244 if remote_dispatcher.options.log_file:
245 console_output('Logging disabled to avoid writing passwords\n')
246 remote_dispatcher.options.log_file = None
248 def complete_set_debug(line, text):
249 if len(line[:-1].split()) >= 2:
250 # Debug value already given in command line
251 return complete_shells(line, text)
252 if text.lower() in ('y', 'n'):
253 return [text + ' ']
254 return ['y ', 'n ']
256 def do_set_debug(command):
258 Usage: :set_debug y|n [SHELLS...]
259 Enable or disable debugging output for remote shells.
260 The first argument is 'y' to enable the debugging output, 'n' to
261 disable it.
262 The remaining optional arguments are the selected shells.
263 The special characters * ? and [] work as expected.
265 split = command.split()
266 if not split:
267 console_output('Expected at least a letter\n')
268 return
269 letter = split[0].lower()
270 if letter not in ('y', 'n'):
271 console_output("Expected 'y' or 'n', got: %s\n" % split[0])
272 return
273 debug = letter == 'y'
274 for i in selected_shells(' '.join(split[1:])):
275 i.debug = debug
277 def complete_replicate(line, text):
278 if ':' not in text:
279 return [c[:-1] + ':' for c in complete_shells(line, text)]
280 shell, path = text.split(':')
281 return [shell + ':' + p for p in complete_local_absolute_path(path)]
283 def do_replicate(command):
285 Usage: :replicate SHELL:path
286 Copy a path from one remote shell to all others
288 if ':' not in command:
289 console_output('Usage: :replicate SHELL:path\n')
290 return
291 shell_name, path = command.split(':', 1)
292 for shell in dispatchers.all_instances():
293 if shell.display_name == shell_name:
294 if not shell.enabled:
295 console_output('%s is not enabled\n' % shell_name)
296 return
297 break
298 else:
299 console_output('%s not found\n' % shell_name)
300 return
301 file_transfer.replicate(shell, path)
303 def do_export_rank(command):
305 Usage: :export_rank
306 Set GSH_RANK and GSH_NR_SHELLS on enabled remote shells.
307 The GSH_RANK shell variable uniquely identifies each shell with a number
308 between 0 and GSH_NR_SHELLS - 1. GSH_NR_SHELLS is the total number of
309 enabled shells.
311 rank = 0
312 for shell in dispatchers.all_instances():
313 if shell.enabled:
314 shell.dispatch_command('export GSH_RANK=%d\n' % rank)
315 rank += 1
317 for shell in dispatchers.all_instances():
318 if shell.enabled:
319 shell.dispatch_command('export GSH_NR_SHELLS=%d\n' % rank)
321 def complete_log_output(line, text):
322 return [p for p in glob.glob(expand_local_path(text or './') + '*')]
324 def do_log_output(command):
326 Usage: :log_output [PATH]
327 Duplicate every console output into the given local file.
328 If PATH is not given, restore the default behaviour of not logging the
329 output.
331 if command:
332 try:
333 remote_dispatcher.options.log_file = file(command, 'a')
334 except IOError, e:
335 console_output('%s\n' % str(e))
336 command = None
337 if not command:
338 remote_dispatcher.options.log_file = None
339 console_output('Logging disabled\n')
341 def main():
343 Output a help text of each control command suitable for the man page
345 for name in list_control_commands():
346 print '<TP>'
347 unstripped = get_control_command(name).__doc__.split('\n')
348 lines = [l.strip() for l in unstripped]
349 usage = lines[1].strip()
350 print '<fB>%s<fR>' % usage[7:]
351 help_text = ' '.join(lines[2:]).replace('gsh', '<fI>gsh<fR>').strip()
352 print help_text
354 if __name__ == '__main__':
355 main()