Added the :print_read_buffer control command to see what gsh just read
[polysh.git] / gsh / control_commands.py
blobcce082d020f83e7314228257dd6fa536ba6921ef
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
22 import shutil
23 import sys
24 import tempfile
26 from gsh.control_commands_helpers import complete_shells, selected_shells
27 from gsh.control_commands_helpers import list_control_commands
28 from gsh.control_commands_helpers import get_control_command, toggle_shells
29 from gsh.control_commands_helpers import expand_local_path
30 from gsh.completion import complete_local_absolute_path
31 from gsh.console import console_output
32 from gsh import dispatchers
33 from gsh import remote_dispatcher
34 from gsh import stdin
35 from gsh import file_transfer
37 def complete_help(line, text):
38 colon = text.startswith(':')
39 text = text.lstrip(':')
40 res = [cmd + ' ' for cmd in list_control_commands() if \
41 cmd.startswith(text) and ' ' + cmd + ' ' not in line]
42 if colon:
43 res = [':' + cmd for cmd in res]
44 return res
46 def do_help(command):
47 """
48 Usage: :help [COMMAND]
49 List control commands or show their documentations.
50 """
51 command = command.strip()
52 if command:
53 texts = []
54 for name in command.split():
55 try:
56 cmd = get_control_command(name.lstrip(':'))
57 except AttributeError:
58 console_output('Unknown control command: %s\n' % name)
59 else:
60 doc = [d.strip() for d in cmd.__doc__.split('\n') if d.strip()]
61 texts.append('\n'.join(doc))
62 if texts:
63 console_output('\n\n'.join(texts))
64 console_output('\n')
65 else:
66 names = list_control_commands()
67 max_name_len = max(map(len, names))
68 for i in xrange(len(names)):
69 name = names[i]
70 txt = (max_name_len - len(name)) * ' ' + ':' + name + ' - '
71 doc = get_control_command(name).__doc__
72 txt += doc.split('\n')[2].strip() + '\n'
73 console_output(txt)
75 def complete_list(line, text):
76 return complete_shells(line, text)
78 def do_list(command):
79 """
80 Usage: :list [SHELLS...]
81 List remote shells and their states.
82 The special characters * ? and [] work as expected.
83 """
84 nr_active = nr_dead = 0
85 instances = []
86 for i in selected_shells(command):
87 instances.append(i.get_info())
88 if i.active:
89 nr_active += 1
90 else:
91 nr_dead += 1
92 dispatchers.format_info(instances)
93 console_output('%s\n\n%d active shells, %d dead shells, total: %d\n' % \
94 ('\n'.join(instances), nr_active, nr_dead, nr_active + nr_dead))
96 def do_quit(command):
97 """
98 Usage: :quit
99 Quit gsh.
101 raise asyncore.ExitNow(0)
103 def complete_chdir(line, text):
104 return [p + '/' for p in glob.glob(expand_local_path(text) + '*') if
105 os.path.isdir(p)]
107 def do_chdir(command):
109 Usage: :chdir PATH
110 Change the current directory of gsh (not the remote shells).
112 try:
113 os.chdir(expand_local_path(command))
114 except OSError, e:
115 console_output('%s\n' % str(e))
117 def complete_send_ctrl(line, text):
118 if len(line[:-1].split()) >= 2:
119 # Control letter already given in command line
120 return complete_shells(line, text, lambda i: i.enabled)
121 if text in ('c', 'd', 'z'):
122 return [text + ' ']
123 return ['c ', 'd ', 'z ']
125 def do_send_ctrl(command):
127 Usage: :send_ctrl LETTER [SHELLS...]
128 Send a control character to remote shells.
129 The first argument is the control character to send: c, d or z.
130 The remaining optional arguments are the destination shells.
131 The special characters * ? and [] work as expected.
133 split = command.split()
134 if not split:
135 console_output('Expected at least a letter\n')
136 return
137 letter = split[0]
138 if len(letter) != 1:
139 console_output('Expected a single letter, got: %s\n' % letter)
140 return
141 control_letter = chr(ord(letter.lower()) - ord('a') + 1)
142 for i in selected_shells(' '.join(split[1:])):
143 if i.enabled:
144 i.dispatch_write(control_letter)
146 def complete_reset_prompt(line, text):
147 return complete_shells(line, text, lambda i: i.enabled)
149 def do_reset_prompt(command):
151 Usage: :reset_prompt [SHELLS...]
152 Change the prompt to be recognized by gsh.
153 The special characters * ? and [] work as expected.
155 for i in selected_shells(command):
156 i.dispatch_command(i.init_string)
158 def complete_enable(line, text):
159 return complete_shells(line, text, lambda i: i.active and not i.enabled)
161 def do_enable(command):
163 Usage: :enable [SHELLS...]
164 Enable sending commands to remote shells.
165 The special characters * ? and [] work as expected.
167 toggle_shells(command, True)
169 def complete_disable(line, text):
170 return complete_shells(line, text, lambda i: i.enabled)
172 def do_disable(command):
174 Usage: :disable [SHELLS...]
175 Disable sending commands to remote shells.
176 The special characters * ? and [] work as expected.
178 toggle_shells(command, False)
180 def complete_reconnect(line, text):
181 return complete_shells(line, text, lambda i: not i.active)
183 def do_reconnect(command):
185 Usage: :reconnect [SHELLS...]
186 Try to reconnect to disconnected remote shells.
187 The special characters * ? and [] work as expected.
189 for i in selected_shells(command):
190 if not i.active:
191 i.reconnect()
193 def do_add(command):
195 Usage: :add NAMES...
196 Add one or many remote shells.
198 for host in command.split():
199 remote_dispatcher.remote_dispatcher(host)
201 def complete_purge(line, text):
202 return complete_shells(line, text, lambda i: not i.enabled)
204 def do_purge(command):
206 Usage: :purge [SHELLS...]
207 Delete disabled remote shells.
208 This helps to have a shorter list.
209 The special characters * ? and [] work as expected.
211 to_delete = []
212 for i in selected_shells(command):
213 if not i.enabled:
214 to_delete.append(i)
215 for i in to_delete:
216 i.disconnect()
217 i.close()
219 def do_rename(command):
221 Usage: :rename [NEW_NAME]
222 Rename all enabled remote shells with the argument.
223 The argument will be shell expanded on the remote processes. With no
224 argument, the original hostname will be restored as the displayed name.
226 for i in dispatchers.all_instances():
227 if i.enabled:
228 i.rename(command)
230 def do_hide_password(command):
232 Usage: :hide_password
233 Do not echo the next typed line.
234 This is useful when entering password. If debugging or logging is enabled,
235 it will be disabled to avoid displaying a password.
237 warned = False
238 for i in dispatchers.all_instances():
239 if i.enabled and i.debug:
240 i.debug = False
241 if not warned:
242 console_output('Debugging disabled to avoid displaying '
243 'passwords\n')
244 warned = True
245 stdin.set_echo(False)
247 if remote_dispatcher.options.log_file:
248 console_output('Logging disabled to avoid writing passwords\n')
249 remote_dispatcher.options.log_file = None
251 def complete_set_debug(line, text):
252 if len(line[:-1].split()) >= 2:
253 # Debug value already given in command line
254 return complete_shells(line, text)
255 if text.lower() in ('y', 'n'):
256 return [text + ' ']
257 return ['y ', 'n ']
259 def do_set_debug(command):
261 Usage: :set_debug y|n [SHELLS...]
262 Enable or disable debugging output for remote shells.
263 The first argument is 'y' to enable the debugging output, 'n' to
264 disable it.
265 The remaining optional arguments are the selected shells.
266 The special characters * ? and [] work as expected.
268 split = command.split()
269 if not split:
270 console_output('Expected at least a letter\n')
271 return
272 letter = split[0].lower()
273 if letter not in ('y', 'n'):
274 console_output("Expected 'y' or 'n', got: %s\n" % split[0])
275 return
276 debug = letter == 'y'
277 for i in selected_shells(' '.join(split[1:])):
278 i.debug = debug
280 def complete_replicate(line, text):
281 if ':' not in text:
282 enabled_shells = complete_shells(line, text, lambda i: i.enabled)
283 return [c[:-1] + ':' for c in enabled_shells]
284 shell, path = text.split(':')
285 return [shell + ':' + p for p in complete_local_absolute_path(path)]
287 def do_replicate(command):
289 Usage: :replicate SHELL:path
290 Copy a path from one remote shell to all others
292 if ':' not in command:
293 console_output('Usage: :replicate SHELL:path\n')
294 return
295 shell_name, path = command.split(':', 1)
296 for shell in dispatchers.all_instances():
297 if shell.display_name == shell_name:
298 if not shell.enabled:
299 console_output('%s is not enabled\n' % shell_name)
300 return
301 break
302 else:
303 console_output('%s not found\n' % shell_name)
304 return
305 file_transfer.replicate(shell, path)
307 def do_export_rank(command):
309 Usage: :export_rank
310 Set GSH_RANK and GSH_NR_SHELLS on enabled remote shells.
311 The GSH_RANK shell variable uniquely identifies each shell with a number
312 between 0 and GSH_NR_SHELLS - 1. GSH_NR_SHELLS is the total number of
313 enabled shells.
315 rank = 0
316 for shell in dispatchers.all_instances():
317 if shell.enabled:
318 shell.dispatch_command('export GSH_RANK=%d\n' % rank)
319 rank += 1
321 for shell in dispatchers.all_instances():
322 if shell.enabled:
323 shell.dispatch_command('export GSH_NR_SHELLS=%d\n' % rank)
325 def complete_log_output(line, text):
326 return [p for p in glob.glob(expand_local_path(text or './') + '*')]
328 def do_log_output(command):
330 Usage: :log_output [PATH]
331 Duplicate every console output into the given local file.
332 If PATH is not given, restore the default behaviour of not logging the
333 output.
335 if command:
336 try:
337 remote_dispatcher.options.log_file = file(command, 'a')
338 except IOError, e:
339 console_output('%s\n' % str(e))
340 command = None
341 if not command:
342 remote_dispatcher.options.log_file = None
343 console_output('Logging disabled\n')
345 def complete_print_read_buffer(line, text):
346 return complete_shells(line, text, lambda i: i.read_buffer or
347 i.read_in_state_not_started)
349 def do_print_read_buffer(command):
351 Usage: :print_read_buffer [SHELLS...]
352 Print the data read by remote shells.
353 The special characters * ? and [] work as expected.
355 for i in selected_shells(command):
356 if i.read_buffer:
357 i.print_lines(i.read_buffer)
358 i.read_buffer = ''
360 if i.read_in_state_not_started:
361 i.print_lines(i.read_in_state_not_started)
362 i.read_in_state_not_started = ''
364 def main():
366 Output a help text of each control command suitable for the man page
367 Run from the gsh top directory: python -m gsh.control_commands
369 try:
370 man_page = file('gsh.1', 'r')
371 except IOError, e:
372 print e
373 print 'Please run "python -m gsh.control_commands" from the gsh top' + \
374 ' directory'
375 sys.exit(1)
377 updated_man_page_fd, updated_man_page_path = tempfile.mkstemp()
378 updated_man_page = os.fdopen(updated_man_page_fd, 'w')
380 for line in man_page:
381 print >> updated_man_page, line,
382 if 'BEGIN AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line:
383 break
385 for name in list_control_commands():
386 print >> updated_man_page, '.TP'
387 unstripped = get_control_command(name).__doc__.split('\n')
388 lines = [l.strip() for l in unstripped]
389 usage = lines[1].strip()
390 print >> updated_man_page, '\\fB%s\\fR' % usage[7:]
391 help_text = ' '.join(lines[2:]).replace('gsh', '\\fIgsh\\fR').strip()
392 print >> updated_man_page, help_text
394 for line in man_page:
395 if 'END AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line:
396 print >> updated_man_page, line,
397 break
399 for line in man_page:
400 print >> updated_man_page, line,
402 man_page.close()
403 updated_man_page.close()
404 shutil.move(updated_man_page_path, 'gsh.1')
406 if __name__ == '__main__':
407 main()