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
.pity
import shell_quote
32 from gsh
.version
import VERSION
33 from gsh
import dispatchers
34 from gsh
import remote_dispatcher
36 from gsh
import file_transfer
38 def complete_help(line
, text
):
39 colon
= text
.startswith(':')
40 text
= text
.lstrip(':')
41 res
= [cmd
+ ' ' for cmd
in list_control_commands() if \
42 cmd
.startswith(text
) and ' ' + cmd
+ ' ' not in line
]
44 res
= [':' + cmd
for cmd
in res
]
49 Usage: :help [COMMAND]
50 List control commands or show their documentations.
52 command
= command
.strip()
55 for name
in command
.split():
57 cmd
= get_control_command(name
.lstrip(':'))
58 except AttributeError:
59 console_output('Unknown control command: %s\n' % name
)
61 doc
= [d
.strip() for d
in cmd
.__doc
__.split('\n') if d
.strip()]
62 texts
.append('\n'.join(doc
))
64 console_output('\n\n'.join(texts
))
67 names
= list_control_commands()
68 max_name_len
= max(map(len, names
))
69 for i
in xrange(len(names
)):
71 txt
= ':' + name
+ (max_name_len
- len(name
) + 2) * ' '
72 doc
= get_control_command(name
).__doc
__
73 txt
+= doc
.split('\n')[2].strip() + '\n'
76 def complete_list(line
, text
):
77 return complete_shells(line
, text
)
81 Usage: :list [SHELLS...]
82 List remote shells and their states.
83 The output consists of: <hostname> <enabled?> <state>: <last printed line>.
84 The special characters * ? and [] work as expected.
86 instances
= [i
.get_info() for i
in selected_shells(command
)]
87 dispatchers
.format_info(instances
)
88 console_output(''.join(instances
))
95 raise asyncore
.ExitNow(0)
97 def complete_chdir(line
, text
):
98 return filter(os
.path
.isdir
, complete_local_path(text
))
100 def do_chdir(command
):
102 Usage: :chdir LOCAL_PATH
103 Change the current directory of gsh (not the remote shells).
106 os
.chdir(expand_local_path(command
.strip()))
108 console_output('%s\n' % str(e
))
110 def complete_send_ctrl(line
, text
):
111 if len(line
[:-1].split()) >= 2:
112 # Control letter already given in command line
113 return complete_shells(line
, text
, lambda i
: i
.enabled
)
114 if text
in ('c', 'd', 'z'):
116 return ['c ', 'd ', 'z ']
118 def do_send_ctrl(command
):
120 Usage: :send_ctrl LETTER [SHELLS...]
121 Send a control character to remote shells.
122 The first argument is the control character to send like c, d or z.
123 Note that these three control characters can be sent simply by typing them
125 The remaining optional arguments are the destination shells.
126 The special characters * ? and [] work as expected.
128 split
= command
.split()
130 console_output('Expected at least a letter\n')
134 console_output('Expected a single letter, got: %s\n' % letter
)
136 control_letter
= chr(ord(letter
.lower()) - ord('a') + 1)
137 for i
in selected_shells(' '.join(split
[1:])):
139 i
.dispatch_write(control_letter
)
141 def complete_reset_prompt(line
, text
):
142 return complete_shells(line
, text
, lambda i
: i
.enabled
)
144 def do_reset_prompt(command
):
146 Usage: :reset_prompt [SHELLS...]
147 Change the prompt to be recognized by gsh.
148 The special characters * ? and [] work as expected.
150 for i
in selected_shells(command
):
151 i
.dispatch_command(i
.init_string
)
153 def complete_enable(line
, text
):
154 return complete_shells(line
, text
, lambda i
:
155 i
.state
!= remote_dispatcher
.STATE_DEAD
)
157 def do_enable(command
):
159 Usage: :enable [SHELLS...]
160 Enable sending commands to remote shells.
161 If the command would have no effect, it changes all other shells to the
162 inverse enable value. That is, if you enable only already enabled
163 shells, it will first disable all other shells.
164 The special characters * ? and [] work as expected.
166 toggle_shells(command
, True)
168 def complete_disable(line
, text
):
169 return complete_shells(line
, text
, lambda i
:
170 i
.state
!= remote_dispatcher
.STATE_DEAD
)
172 def do_disable(command
):
174 Usage: :disable [SHELLS...]
175 Disable sending commands to remote shells.
176 If the command would have no effect, it changes all other shells to the
177 inverse enable value. That is, if you disable only already disabled
178 shells, it will first enable all other shells.
179 The special characters * ? and [] work as expected.
181 toggle_shells(command
, False)
183 def complete_reconnect(line
, text
):
184 return complete_shells(line
, text
, lambda i
:
185 i
.state
== remote_dispatcher
.STATE_DEAD
)
187 def do_reconnect(command
):
189 Usage: :reconnect [SHELLS...]
190 Try to reconnect to disconnected remote shells.
191 The special characters * ? and [] work as expected.
193 for i
in selected_shells(command
):
194 if i
.state
== remote_dispatcher
.STATE_DEAD
:
200 Add one or many remote shells.
202 for host
in command
.split():
203 remote_dispatcher
.remote_dispatcher(host
)
205 def complete_purge(line
, text
):
206 return complete_shells(line
, text
, lambda i
: not i
.enabled
)
208 def do_purge(command
):
210 Usage: :purge [SHELLS...]
211 Delete disabled remote shells.
212 This helps to have a shorter list.
213 The special characters * ? and [] work as expected.
216 for i
in selected_shells(command
):
223 def do_rename(command
):
225 Usage: :rename [NEW_NAME]
226 Rename all enabled remote shells with the argument.
227 The argument will be shell expanded on the remote processes. With no
228 argument, the original hostname will be restored as the displayed name.
230 for i
in dispatchers
.all_instances():
234 def do_hide_password(command
):
236 Usage: :hide_password
237 Do not echo the next typed line.
238 This is useful when entering password. If debugging or logging is enabled,
239 it will be disabled to avoid displaying a password. Therefore, you will have
240 to reenable logging or debugging afterwards if need be.
243 for i
in dispatchers
.all_instances():
244 if i
.enabled
and i
.debug
:
247 console_output('Debugging disabled to avoid displaying '
250 stdin
.set_echo(False)
252 if remote_dispatcher
.options
.log_file
:
253 console_output('Logging disabled to avoid writing passwords\n')
254 remote_dispatcher
.options
.log_file
= None
256 def complete_set_debug(line
, text
):
257 if len(line
[:-1].split()) >= 2:
258 # Debug value already given in command line
259 return complete_shells(line
, text
)
260 if text
.lower() in ('y', 'n'):
264 def do_set_debug(command
):
266 Usage: :set_debug y|n [SHELLS...]
267 Enable or disable debugging output for remote shells.
268 The first argument is 'y' to enable the debugging output, 'n' to
270 The remaining optional arguments are the selected shells.
271 The special characters * ? and [] work as expected.
273 split
= command
.split()
275 console_output('Expected at least a letter\n')
277 letter
= split
[0].lower()
278 if letter
not in ('y', 'n'):
279 console_output("Expected 'y' or 'n', got: %s\n" % split
[0])
281 debug
= letter
== 'y'
282 for i
in selected_shells(' '.join(split
[1:])):
285 def complete_replicate(line
, text
):
287 enabled_shells
= complete_shells(line
, text
, lambda i
: i
.enabled
)
288 return [c
[:-1] + ':' for c
in enabled_shells
]
289 shell
, path
= text
.split(':')
290 return [shell
+ ':' + p
for p
in complete_local_path(path
)]
292 def do_replicate(command
):
294 Usage: :replicate SHELL:REMOTE_PATH
295 Copy a path from one remote shell to all others
297 if ':' not in command
:
298 console_output('Usage: :replicate SHELL:REMOTE_PATH\n')
300 shell_name
, path
= command
.strip().split(':', 1)
302 console_output('No remote path given\n')
304 for shell
in dispatchers
.all_instances():
305 if shell
.display_name
== shell_name
:
306 if not shell
.enabled
:
307 console_output('%s is not enabled\n' % shell_name
)
311 console_output('%s not found\n' % shell_name
)
313 file_transfer
.replicate(shell
, path
)
315 def complete_upload(line
, text
):
316 return complete_local_path(text
)
318 def do_upload(command
):
320 Usage: :upload LOCAL_PATH
321 Upload the specified local path to enabled remote shells.
324 file_transfer
.upload(command
.strip())
326 console_output('No local path given\n')
328 def do_export_vars(command
):
331 Export some environment variables on enabled remote shells. GSH_NR_SHELLS
332 is the total number of enabled shells. GSH_RANK uniquely identifies each
333 shell with a number between 0 and GSH_NR_SHELLS - 1. GSH_NAME is the
334 hostname as specified on the command line and GSH_DISPLAY_NAME the
335 hostname as displayed by :list (most of the time the same as GSH_NAME).
338 for shell
in dispatchers
.all_instances():
340 environment_variables
= {
342 'GSH_NAME': shell
.hostname
,
343 'GSH_DISPLAY_NAME': shell
.display_name
,
345 for name
, value
in environment_variables
.iteritems():
346 value
= shell_quote(str(value
))
347 shell
.dispatch_command('export %s=%s\n' % (name
, value
))
350 for shell
in dispatchers
.all_instances():
352 shell
.dispatch_command('export GSH_NR_SHELLS=%d\n' % rank
)
354 def complete_set_log(line
, text
):
355 return complete_local_path(text
)
357 def do_set_log(command
):
359 Usage: :set_log [LOCAL_PATH]
360 Duplicate every console I/O into the given local file.
361 If LOCAL_PATH is not given, restore the default behaviour of not logging.
363 command
= command
.strip()
366 remote_dispatcher
.options
.log_file
= file(command
, 'a')
368 console_output('%s\n' % str(e
))
371 remote_dispatcher
.options
.log_file
= None
372 console_output('Logging disabled\n')
374 def complete_show_read_buffer(line
, text
):
375 return complete_shells(line
, text
, lambda i
: i
.read_buffer
or
376 i
.read_in_state_not_started
)
378 def do_show_read_buffer(command
):
380 Usage: :show_read_buffer [SHELLS...]
381 Print the data read by remote shells.
382 The special characters * ? and [] work as expected.
384 for i
in selected_shells(command
):
385 if i
.read_in_state_not_started
:
386 i
.print_lines(i
.read_in_state_not_started
)
387 i
.read_in_state_not_started
= ''
391 Output a help text of each control command suitable for the man page
392 Run from the gsh top directory: python -m gsh.control_commands
395 man_page
= file('gsh.1', 'r')
398 print 'Please run "python -m gsh.control_commands" from the gsh top' + \
402 updated_man_page_fd
, updated_man_page_path
= tempfile
.mkstemp()
403 updated_man_page
= os
.fdopen(updated_man_page_fd
, 'w')
405 # The first line is auto-generated as it contains the version number
407 v
= '.TH "gsh" "1" "%s" "Guillaume Chazarain" "Remote shells"' % VERSION
408 print >> updated_man_page
, v
410 for line
in man_page
:
411 print >> updated_man_page
, line
,
412 if 'BEGIN AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line
:
415 for name
in list_control_commands():
416 print >> updated_man_page
, '.TP'
417 unstripped
= get_control_command(name
).__doc
__.split('\n')
418 lines
= [l
.strip() for l
in unstripped
]
419 usage
= lines
[1].strip()
420 print >> updated_man_page
, '\\fB%s\\fR' % usage
[7:]
421 help_text
= ' '.join(lines
[2:]).replace('gsh', '\\fIgsh\\fR').strip()
422 print >> updated_man_page
, help_text
424 for line
in man_page
:
425 if 'END AUTO-GENERATED CONTROL COMMANDS DOCUMENTATION' in line
:
426 print >> updated_man_page
, line
,
429 for line
in man_page
:
430 print >> updated_man_page
, line
,
433 updated_man_page
.close()
434 shutil
.move(updated_man_page_path
, 'gsh.1')
436 if __name__
== '__main__':