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>
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
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
]
43 res
= [':' + cmd
for cmd
in res
]
48 Usage: :help [COMMAND]
49 List control commands or show their documentations.
51 command
= command
.strip()
54 for name
in command
.split():
56 cmd
= get_control_command(name
.lstrip(':'))
57 except AttributeError:
58 console_output('Unknown control command: %s\n' % name
)
60 doc
= [d
.strip() for d
in cmd
.__doc
__.split('\n') if d
.strip()]
61 texts
.append('\n'.join(doc
))
63 console_output('\n\n'.join(texts
))
66 names
= list_control_commands()
67 max_name_len
= max(map(len, names
))
68 for i
in xrange(len(names
)):
70 txt
= (max_name_len
- len(name
)) * ' ' + ':' + name
+ ' - '
71 doc
= get_control_command(name
).__doc
__
72 txt
+= doc
.split('\n')[2].strip() + '\n'
75 def complete_list(line
, text
):
76 return complete_shells(line
, text
)
80 Usage: :list [SHELLS...]
81 List remote shells and their states.
82 The special characters * ? and [] work as expected.
84 nr_active
= nr_dead
= 0
86 for i
in selected_shells(command
):
87 instances
.append(i
.get_info())
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
))
101 raise asyncore
.ExitNow(0)
103 def complete_chdir(line
, text
):
104 return [p
+ '/' for p
in glob
.glob(expand_local_path(text
) + '*') if
107 def do_chdir(command
):
110 Change the current directory of gsh (not the remote shells).
113 os
.chdir(expand_local_path(command
))
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'):
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()
135 console_output('Expected at least a letter\n')
139 console_output('Expected a single letter, got: %s\n' % letter
)
141 control_letter
= chr(ord(letter
.lower()) - ord('a') + 1)
142 for i
in selected_shells(' '.join(split
[1:])):
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
):
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.
212 for i
in selected_shells(command
):
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():
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.
238 for i
in dispatchers
.all_instances():
239 if i
.enabled
and i
.debug
:
242 console_output('Debugging disabled to avoid displaying '
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'):
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
265 The remaining optional arguments are the selected shells.
266 The special characters * ? and [] work as expected.
268 split
= command
.split()
270 console_output('Expected at least a letter\n')
272 letter
= split
[0].lower()
273 if letter
not in ('y', 'n'):
274 console_output("Expected 'y' or 'n', got: %s\n" % split
[0])
276 debug
= letter
== 'y'
277 for i
in selected_shells(' '.join(split
[1:])):
280 def complete_replicate(line
, 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')
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
)
303 console_output('%s not found\n' % shell_name
)
305 file_transfer
.replicate(shell
, path
)
307 def do_export_rank(command
):
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
316 for shell
in dispatchers
.all_instances():
318 shell
.dispatch_command('export GSH_RANK=%d\n' % rank
)
321 for shell
in dispatchers
.all_instances():
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
337 remote_dispatcher
.options
.log_file
= file(command
, 'a')
339 console_output('%s\n' % str(e
))
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
):
357 i
.print_lines(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
= ''
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
370 man_page
= file('gsh.1', 'r')
373 print 'Please run "python -m gsh.control_commands" from the gsh top' + \
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
:
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
,
399 for line
in man_page
:
400 print >> updated_man_page
, line
,
403 updated_man_page
.close()
404 shutil
.move(updated_man_page_path
, 'gsh.1')
406 if __name__
== '__main__':