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>
25 from gsh
.control_commands_helpers
import complete_shells
, selected_shells
26 from gsh
.control_commands_helpers
import list_control_commands
27 from gsh
.control_commands_helpers
import get_control_command
, toggle_shells
28 from gsh
.control_commands_helpers
import expand_local_path
29 from gsh
.completion
import complete_local_path
30 from gsh
.console
import console_output
31 from gsh
import dispatchers
32 from gsh
import remote_dispatcher
34 from gsh
import file_transfer
36 def complete_help(line
, text
):
37 colon
= text
.startswith(':')
38 text
= text
.lstrip(':')
39 res
= [cmd
+ ' ' for cmd
in list_control_commands() if \
40 cmd
.startswith(text
) and ' ' + cmd
+ ' ' not in line
]
42 res
= [':' + cmd
for cmd
in res
]
47 Usage: :help [COMMAND]
48 List control commands or show their documentations.
50 command
= command
.strip()
53 for name
in command
.split():
55 cmd
= get_control_command(name
.lstrip(':'))
56 except AttributeError:
57 console_output('Unknown control command: %s\n' % name
)
59 doc
= [d
.strip() for d
in cmd
.__doc
__.split('\n') if d
.strip()]
60 texts
.append('\n'.join(doc
))
62 console_output('\n\n'.join(texts
))
65 names
= list_control_commands()
66 max_name_len
= max(map(len, names
))
67 for i
in xrange(len(names
)):
69 txt
= (max_name_len
- len(name
)) * ' ' + ':' + name
+ ' - '
70 doc
= get_control_command(name
).__doc
__
71 txt
+= doc
.split('\n')[2].strip() + '\n'
74 def complete_list(line
, text
):
75 return complete_shells(line
, text
)
79 Usage: :list [SHELLS...]
80 List remote shells and their states.
81 The output consists of: <hostname> <enabled?> <state>: <last printed line>.
82 The special characters * ? and [] work as expected.
84 instances
= [i
.get_info() for i
in selected_shells(command
)]
85 dispatchers
.format_info(instances
)
86 console_output(''.join(instances
))
93 raise asyncore
.ExitNow(0)
95 def complete_chdir(line
, text
):
96 return filter(os
.path
.isdir
, complete_local_path(text
))
98 def do_chdir(command
):
100 Usage: :chdir LOCAL_PATH
101 Change the current directory of gsh (not the remote shells).
104 os
.chdir(expand_local_path(command
))
106 console_output('%s\n' % str(e
))
108 def complete_send_ctrl(line
, text
):
109 if len(line
[:-1].split()) >= 2:
110 # Control letter already given in command line
111 return complete_shells(line
, text
, lambda i
: i
.enabled
)
112 if text
in ('c', 'd', 'z'):
114 return ['c ', 'd ', 'z ']
116 def do_send_ctrl(command
):
118 Usage: :send_ctrl LETTER [SHELLS...]
119 Send a control character to remote shells.
120 The first argument is the control character to send like c, d or z.
121 Note that these three control characters can be sent simply by typing them
123 The remaining optional arguments are the destination shells.
124 The special characters * ? and [] work as expected.
126 split
= command
.split()
128 console_output('Expected at least a letter\n')
132 console_output('Expected a single letter, got: %s\n' % letter
)
134 control_letter
= chr(ord(letter
.lower()) - ord('a') + 1)
135 for i
in selected_shells(' '.join(split
[1:])):
137 i
.dispatch_write(control_letter
)
139 def complete_reset_prompt(line
, text
):
140 return complete_shells(line
, text
, lambda i
: i
.enabled
)
142 def do_reset_prompt(command
):
144 Usage: :reset_prompt [SHELLS...]
145 Change the prompt to be recognized by gsh.
146 The special characters * ? and [] work as expected.
148 for i
in selected_shells(command
):
149 i
.dispatch_command(i
.init_string
)
151 def complete_enable(line
, text
):
152 return complete_shells(line
, text
, lambda i
:
153 i
.state
!= remote_dispatcher
.STATE_DEAD
and not i
.enabled
)
155 def do_enable(command
):
157 Usage: :enable [SHELLS...]
158 Enable sending commands to remote shells.
159 The special characters * ? and [] work as expected.
161 toggle_shells(command
, True)
163 def complete_disable(line
, text
):
164 return complete_shells(line
, text
, lambda i
: i
.enabled
)
166 def do_disable(command
):
168 Usage: :disable [SHELLS...]
169 Disable sending commands to remote shells.
170 The special characters * ? and [] work as expected.
172 toggle_shells(command
, False)
174 def complete_reconnect(line
, text
):
175 return complete_shells(line
, text
, lambda i
:
176 i
.state
== remote_dispatcher
.STATE_DEAD
)
178 def do_reconnect(command
):
180 Usage: :reconnect [SHELLS...]
181 Try to reconnect to disconnected remote shells.
182 The special characters * ? and [] work as expected.
184 for i
in selected_shells(command
):
185 if i
.state
== remote_dispatcher
.STATE_DEAD
:
191 Add one or many remote shells.
193 for host
in command
.split():
194 remote_dispatcher
.remote_dispatcher(host
)
196 def complete_purge(line
, text
):
197 return complete_shells(line
, text
, lambda i
: not i
.enabled
)
199 def do_purge(command
):
201 Usage: :purge [SHELLS...]
202 Delete disabled remote shells.
203 This helps to have a shorter list.
204 The special characters * ? and [] work as expected.
207 for i
in selected_shells(command
):
214 def do_rename(command
):
216 Usage: :rename [NEW_NAME]
217 Rename all enabled remote shells with the argument.
218 The argument will be shell expanded on the remote processes. With no
219 argument, the original hostname will be restored as the displayed name.
221 for i
in dispatchers
.all_instances():
225 def do_hide_password(command
):
227 Usage: :hide_password
228 Do not echo the next typed line.
229 This is useful when entering password. If debugging or logging is enabled,
230 it will be disabled to avoid displaying a password. Therefore, you will have
231 to reenable logging or debugging afterwards if need be.
234 for i
in dispatchers
.all_instances():
235 if i
.enabled
and i
.debug
:
238 console_output('Debugging disabled to avoid displaying '
241 stdin
.set_echo(False)
243 if remote_dispatcher
.options
.log_file
:
244 console_output('Logging disabled to avoid writing passwords\n')
245 remote_dispatcher
.options
.log_file
= None
247 def complete_set_debug(line
, text
):
248 if len(line
[:-1].split()) >= 2:
249 # Debug value already given in command line
250 return complete_shells(line
, text
)
251 if text
.lower() in ('y', 'n'):
255 def do_set_debug(command
):
257 Usage: :set_debug y|n [SHELLS...]
258 Enable or disable debugging output for remote shells.
259 The first argument is 'y' to enable the debugging output, 'n' to
261 The remaining optional arguments are the selected shells.
262 The special characters * ? and [] work as expected.
264 split
= command
.split()
266 console_output('Expected at least a letter\n')
268 letter
= split
[0].lower()
269 if letter
not in ('y', 'n'):
270 console_output("Expected 'y' or 'n', got: %s\n" % split
[0])
272 debug
= letter
== 'y'
273 for i
in selected_shells(' '.join(split
[1:])):
276 def complete_replicate(line
, text
):
278 enabled_shells
= complete_shells(line
, text
, lambda i
: i
.enabled
)
279 return [c
[:-1] + ':' for c
in enabled_shells
]
280 shell
, path
= text
.split(':')
281 return [shell
+ ':' + p
for p
in complete_local_path(path
)]
283 def do_replicate(command
):
285 Usage: :replicate SHELL:REMOTE_PATH
286 Copy a path from one remote shell to all others
288 if ':' not in command
:
289 console_output('Usage: :replicate SHELL:REMOTE_PATH\n')
291 shell_name
, path
= command
.split(':', 1)
293 console_output('No remote path given\n')
295 for shell
in dispatchers
.all_instances():
296 if shell
.display_name
== shell_name
:
297 if not shell
.enabled
:
298 console_output('%s is not enabled\n' % shell_name
)
302 console_output('%s not found\n' % shell_name
)
304 file_transfer
.replicate(shell
, path
)
306 def complete_upload(line
, text
):
307 return complete_local_path(text
)
309 def do_upload(command
):
311 Usage: :upload LOCAL_PATH
312 Upload the specified local path to enabled remote shells.
315 file_transfer
.upload(command
)
317 console_output('No local path given\n')
319 def do_export_rank(command
):
322 Set GSH_RANK and GSH_NR_SHELLS on enabled remote shells.
323 The GSH_RANK shell variable uniquely identifies each shell with a number
324 between 0 and GSH_NR_SHELLS - 1. GSH_NR_SHELLS is the total number of
328 for shell
in dispatchers
.all_instances():
330 shell
.dispatch_command('export GSH_RANK=%d\n' % rank
)
333 for shell
in dispatchers
.all_instances():
335 shell
.dispatch_command('export GSH_NR_SHELLS=%d\n' % rank
)
337 def complete_set_log(line
, text
):
338 return complete_local_path(text
)
340 def do_set_log(command
):
342 Usage: :set_log [LOCAL_PATH]
343 Duplicate every console input/output into the given local file.
344 If LOCAL_PATH is not given, restore the default behaviour of not logging.
348 remote_dispatcher
.options
.log_file
= file(command
, 'a')
350 console_output('%s\n' % str(e
))
353 remote_dispatcher
.options
.log_file
= None
354 console_output('Logging disabled\n')
356 def complete_show_read_buffer(line
, text
):
357 return complete_shells(line
, text
, lambda i
: i
.read_buffer
or
358 i
.read_in_state_not_started
)
360 def do_show_read_buffer(command
):
362 Usage: :show_read_buffer [SHELLS...]
363 Print the data read by remote shells.
364 The special characters * ? and [] work as expected.
366 for i
in selected_shells(command
):
367 if i
.read_in_state_not_started
:
368 i
.print_lines(i
.read_in_state_not_started
)
369 i
.read_in_state_not_started
= ''
373 Output a help text of each control command suitable for the man page
374 Run from the gsh top directory: python -m gsh.control_commands
377 man_page
= file('gsh.1', 'r')
380 print 'Please run "python -m gsh.control_commands" from the gsh top' + \
384 updated_man_page_fd
, updated_man_page_path
= tempfile
.mkstemp()
385 updated_man_page
= os
.fdopen(updated_man_page_fd
, 'w')
387 for line
in man_page
:
388 print >> updated_man_page
, line
,
389 if 'BEGIN AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line
:
392 for name
in list_control_commands():
393 print >> updated_man_page
, '.TP'
394 unstripped
= get_control_command(name
).__doc
__.split('\n')
395 lines
= [l
.strip() for l
in unstripped
]
396 usage
= lines
[1].strip()
397 print >> updated_man_page
, '\\fB%s\\fR' % usage
[7:]
398 help_text
= ' '.join(lines
[2:]).replace('gsh', '\\fIgsh\\fR').strip()
399 print >> updated_man_page
, help_text
401 for line
in man_page
:
402 if 'END AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line
:
403 print >> updated_man_page
, line
,
406 for line
in man_page
:
407 print >> updated_man_page
, line
,
410 updated_man_page
.close()
411 shutil
.move(updated_man_page_path
, 'gsh.1')
413 if __name__
== '__main__':