##// END OF EJS Templates
Brian Granger -
Show More
@@ -1,2161 +1,2171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """General purpose utilities.
2 """General purpose utilities.
3
3
4 This is a grab-bag of stuff I find useful in most programs I write. Some of
4 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 these things are also convenient when working at the command line.
5 these things are also convenient when working at the command line.
6 """
6 """
7
7
8 #*****************************************************************************
8 #*****************************************************************************
9 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #*****************************************************************************
13 #*****************************************************************************
14
14
15 #****************************************************************************
15 #****************************************************************************
16 # required modules from the Python standard library
16 # required modules from the Python standard library
17 import __main__
17 import __main__
18 import commands
18 import commands
19 try:
19 try:
20 import doctest
20 import doctest
21 except ImportError:
21 except ImportError:
22 pass
22 pass
23 import os
23 import os
24 import platform
24 import platform
25 import re
25 import re
26 import shlex
26 import shlex
27 import shutil
27 import shutil
28 import subprocess
28 import subprocess
29 import sys
29 import sys
30 import tempfile
30 import tempfile
31 import time
31 import time
32 import types
32 import types
33 import warnings
33 import warnings
34
34
35 # Curses and termios are Unix-only modules
35 # Curses and termios are Unix-only modules
36 try:
36 try:
37 import curses
37 import curses
38 # We need termios as well, so if its import happens to raise, we bail on
38 # We need termios as well, so if its import happens to raise, we bail on
39 # using curses altogether.
39 # using curses altogether.
40 import termios
40 import termios
41 except ImportError:
41 except ImportError:
42 USE_CURSES = False
42 USE_CURSES = False
43 else:
43 else:
44 # Curses on Solaris may not be complete, so we can't use it there
44 # Curses on Solaris may not be complete, so we can't use it there
45 USE_CURSES = hasattr(curses,'initscr')
45 USE_CURSES = hasattr(curses,'initscr')
46
46
47 # Other IPython utilities
47 # Other IPython utilities
48 import IPython
48 import IPython
49 from IPython.Itpl import Itpl,itpl,printpl
49 from IPython.Itpl import Itpl,itpl,printpl
50 from IPython import DPyGetOpt, platutils
50 from IPython import DPyGetOpt, platutils
51 from IPython.generics import result_display
51 from IPython.generics import result_display
52 import IPython.ipapi
52 import IPython.ipapi
53 from IPython.external.path import path
53 from IPython.external.path import path
54 if os.name == "nt":
54 if os.name == "nt":
55 from IPython.winconsole import get_console_size
55 from IPython.winconsole import get_console_size
56
56
57 try:
57 try:
58 set
58 set
59 except:
59 except:
60 from sets import Set as set
60 from sets import Set as set
61
61
62
62
63 #****************************************************************************
63 #****************************************************************************
64 # Exceptions
64 # Exceptions
65 class Error(Exception):
65 class Error(Exception):
66 """Base class for exceptions in this module."""
66 """Base class for exceptions in this module."""
67 pass
67 pass
68
68
69 #----------------------------------------------------------------------------
69 #----------------------------------------------------------------------------
70 class IOStream:
70 class IOStream:
71 def __init__(self,stream,fallback):
71 def __init__(self,stream,fallback):
72 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
72 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
73 stream = fallback
73 stream = fallback
74 self.stream = stream
74 self.stream = stream
75 self._swrite = stream.write
75 self._swrite = stream.write
76 self.flush = stream.flush
76 self.flush = stream.flush
77
77
78 def write(self,data):
78 def write(self,data):
79 try:
79 try:
80 self._swrite(data)
80 self._swrite(data)
81 except:
81 except:
82 try:
82 try:
83 # print handles some unicode issues which may trip a plain
83 # print handles some unicode issues which may trip a plain
84 # write() call. Attempt to emulate write() by using a
84 # write() call. Attempt to emulate write() by using a
85 # trailing comma
85 # trailing comma
86 print >> self.stream, data,
86 print >> self.stream, data,
87 except:
87 except:
88 # if we get here, something is seriously broken.
88 # if we get here, something is seriously broken.
89 print >> sys.stderr, \
89 print >> sys.stderr, \
90 'ERROR - failed to write data to stream:', self.stream
90 'ERROR - failed to write data to stream:', self.stream
91
91
92 def close(self):
92 def close(self):
93 pass
93 pass
94
94
95
95
96 class IOTerm:
96 class IOTerm:
97 """ Term holds the file or file-like objects for handling I/O operations.
97 """ Term holds the file or file-like objects for handling I/O operations.
98
98
99 These are normally just sys.stdin, sys.stdout and sys.stderr but for
99 These are normally just sys.stdin, sys.stdout and sys.stderr but for
100 Windows they can can replaced to allow editing the strings before they are
100 Windows they can can replaced to allow editing the strings before they are
101 displayed."""
101 displayed."""
102
102
103 # In the future, having IPython channel all its I/O operations through
103 # In the future, having IPython channel all its I/O operations through
104 # this class will make it easier to embed it into other environments which
104 # this class will make it easier to embed it into other environments which
105 # are not a normal terminal (such as a GUI-based shell)
105 # are not a normal terminal (such as a GUI-based shell)
106 def __init__(self,cin=None,cout=None,cerr=None):
106 def __init__(self,cin=None,cout=None,cerr=None):
107 self.cin = IOStream(cin,sys.stdin)
107 self.cin = IOStream(cin,sys.stdin)
108 self.cout = IOStream(cout,sys.stdout)
108 self.cout = IOStream(cout,sys.stdout)
109 self.cerr = IOStream(cerr,sys.stderr)
109 self.cerr = IOStream(cerr,sys.stderr)
110
110
111 # Global variable to be used for all I/O
111 # Global variable to be used for all I/O
112 Term = IOTerm()
112 Term = IOTerm()
113
113
114 import IPython.rlineimpl as readline
114 import IPython.rlineimpl as readline
115 # Remake Term to use the readline i/o facilities
115 # Remake Term to use the readline i/o facilities
116 if sys.platform == 'win32' and readline.have_readline:
116 if sys.platform == 'win32' and readline.have_readline:
117
117
118 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
118 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
119
119
120
120
121 #****************************************************************************
121 #****************************************************************************
122 # Generic warning/error printer, used by everything else
122 # Generic warning/error printer, used by everything else
123 def warn(msg,level=2,exit_val=1):
123 def warn(msg,level=2,exit_val=1):
124 """Standard warning printer. Gives formatting consistency.
124 """Standard warning printer. Gives formatting consistency.
125
125
126 Output is sent to Term.cerr (sys.stderr by default).
126 Output is sent to Term.cerr (sys.stderr by default).
127
127
128 Options:
128 Options:
129
129
130 -level(2): allows finer control:
130 -level(2): allows finer control:
131 0 -> Do nothing, dummy function.
131 0 -> Do nothing, dummy function.
132 1 -> Print message.
132 1 -> Print message.
133 2 -> Print 'WARNING:' + message. (Default level).
133 2 -> Print 'WARNING:' + message. (Default level).
134 3 -> Print 'ERROR:' + message.
134 3 -> Print 'ERROR:' + message.
135 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
135 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
136
136
137 -exit_val (1): exit value returned by sys.exit() for a level 4
137 -exit_val (1): exit value returned by sys.exit() for a level 4
138 warning. Ignored for all other levels."""
138 warning. Ignored for all other levels."""
139
139
140 if level>0:
140 if level>0:
141 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
141 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
142 print >> Term.cerr, '%s%s' % (header[level],msg)
142 print >> Term.cerr, '%s%s' % (header[level],msg)
143 if level == 4:
143 if level == 4:
144 print >> Term.cerr,'Exiting.\n'
144 print >> Term.cerr,'Exiting.\n'
145 sys.exit(exit_val)
145 sys.exit(exit_val)
146
146
147 def info(msg):
147 def info(msg):
148 """Equivalent to warn(msg,level=1)."""
148 """Equivalent to warn(msg,level=1)."""
149
149
150 warn(msg,level=1)
150 warn(msg,level=1)
151
151
152 def error(msg):
152 def error(msg):
153 """Equivalent to warn(msg,level=3)."""
153 """Equivalent to warn(msg,level=3)."""
154
154
155 warn(msg,level=3)
155 warn(msg,level=3)
156
156
157 def fatal(msg,exit_val=1):
157 def fatal(msg,exit_val=1):
158 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
158 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
159
159
160 warn(msg,exit_val=exit_val,level=4)
160 warn(msg,exit_val=exit_val,level=4)
161
161
162 #---------------------------------------------------------------------------
162 #---------------------------------------------------------------------------
163 # Debugging routines
163 # Debugging routines
164 #
164 #
165 def debugx(expr,pre_msg=''):
165 def debugx(expr,pre_msg=''):
166 """Print the value of an expression from the caller's frame.
166 """Print the value of an expression from the caller's frame.
167
167
168 Takes an expression, evaluates it in the caller's frame and prints both
168 Takes an expression, evaluates it in the caller's frame and prints both
169 the given expression and the resulting value (as well as a debug mark
169 the given expression and the resulting value (as well as a debug mark
170 indicating the name of the calling function. The input must be of a form
170 indicating the name of the calling function. The input must be of a form
171 suitable for eval().
171 suitable for eval().
172
172
173 An optional message can be passed, which will be prepended to the printed
173 An optional message can be passed, which will be prepended to the printed
174 expr->value pair."""
174 expr->value pair."""
175
175
176 cf = sys._getframe(1)
176 cf = sys._getframe(1)
177 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
177 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
178 eval(expr,cf.f_globals,cf.f_locals))
178 eval(expr,cf.f_globals,cf.f_locals))
179
179
180 # deactivate it by uncommenting the following line, which makes it a no-op
180 # deactivate it by uncommenting the following line, which makes it a no-op
181 #def debugx(expr,pre_msg=''): pass
181 #def debugx(expr,pre_msg=''): pass
182
182
183 #----------------------------------------------------------------------------
183 #----------------------------------------------------------------------------
184 StringTypes = types.StringTypes
184 StringTypes = types.StringTypes
185
185
186 # Basic timing functionality
186 # Basic timing functionality
187
187
188 # If possible (Unix), use the resource module instead of time.clock()
188 # If possible (Unix), use the resource module instead of time.clock()
189 try:
189 try:
190 import resource
190 import resource
191 def clocku():
191 def clocku():
192 """clocku() -> floating point number
192 """clocku() -> floating point number
193
193
194 Return the *USER* CPU time in seconds since the start of the process.
194 Return the *USER* CPU time in seconds since the start of the process.
195 This is done via a call to resource.getrusage, so it avoids the
195 This is done via a call to resource.getrusage, so it avoids the
196 wraparound problems in time.clock()."""
196 wraparound problems in time.clock()."""
197
197
198 return resource.getrusage(resource.RUSAGE_SELF)[0]
198 return resource.getrusage(resource.RUSAGE_SELF)[0]
199
199
200 def clocks():
200 def clocks():
201 """clocks() -> floating point number
201 """clocks() -> floating point number
202
202
203 Return the *SYSTEM* CPU time in seconds since the start of the process.
203 Return the *SYSTEM* CPU time in seconds since the start of the process.
204 This is done via a call to resource.getrusage, so it avoids the
204 This is done via a call to resource.getrusage, so it avoids the
205 wraparound problems in time.clock()."""
205 wraparound problems in time.clock()."""
206
206
207 return resource.getrusage(resource.RUSAGE_SELF)[1]
207 return resource.getrusage(resource.RUSAGE_SELF)[1]
208
208
209 def clock():
209 def clock():
210 """clock() -> floating point number
210 """clock() -> floating point number
211
211
212 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
212 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
213 the process. This is done via a call to resource.getrusage, so it
213 the process. This is done via a call to resource.getrusage, so it
214 avoids the wraparound problems in time.clock()."""
214 avoids the wraparound problems in time.clock()."""
215
215
216 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
216 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
217 return u+s
217 return u+s
218
218
219 def clock2():
219 def clock2():
220 """clock2() -> (t_user,t_system)
220 """clock2() -> (t_user,t_system)
221
221
222 Similar to clock(), but return a tuple of user/system times."""
222 Similar to clock(), but return a tuple of user/system times."""
223 return resource.getrusage(resource.RUSAGE_SELF)[:2]
223 return resource.getrusage(resource.RUSAGE_SELF)[:2]
224
224
225 except ImportError:
225 except ImportError:
226 # There is no distinction of user/system time under windows, so we just use
226 # There is no distinction of user/system time under windows, so we just use
227 # time.clock() for everything...
227 # time.clock() for everything...
228 clocku = clocks = clock = time.clock
228 clocku = clocks = clock = time.clock
229 def clock2():
229 def clock2():
230 """Under windows, system CPU time can't be measured.
230 """Under windows, system CPU time can't be measured.
231
231
232 This just returns clock() and zero."""
232 This just returns clock() and zero."""
233 return time.clock(),0.0
233 return time.clock(),0.0
234
234
235 def timings_out(reps,func,*args,**kw):
235 def timings_out(reps,func,*args,**kw):
236 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
236 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
237
237
238 Execute a function reps times, return a tuple with the elapsed total
238 Execute a function reps times, return a tuple with the elapsed total
239 CPU time in seconds, the time per call and the function's output.
239 CPU time in seconds, the time per call and the function's output.
240
240
241 Under Unix, the return value is the sum of user+system time consumed by
241 Under Unix, the return value is the sum of user+system time consumed by
242 the process, computed via the resource module. This prevents problems
242 the process, computed via the resource module. This prevents problems
243 related to the wraparound effect which the time.clock() function has.
243 related to the wraparound effect which the time.clock() function has.
244
244
245 Under Windows the return value is in wall clock seconds. See the
245 Under Windows the return value is in wall clock seconds. See the
246 documentation for the time module for more details."""
246 documentation for the time module for more details."""
247
247
248 reps = int(reps)
248 reps = int(reps)
249 assert reps >=1, 'reps must be >= 1'
249 assert reps >=1, 'reps must be >= 1'
250 if reps==1:
250 if reps==1:
251 start = clock()
251 start = clock()
252 out = func(*args,**kw)
252 out = func(*args,**kw)
253 tot_time = clock()-start
253 tot_time = clock()-start
254 else:
254 else:
255 rng = xrange(reps-1) # the last time is executed separately to store output
255 rng = xrange(reps-1) # the last time is executed separately to store output
256 start = clock()
256 start = clock()
257 for dummy in rng: func(*args,**kw)
257 for dummy in rng: func(*args,**kw)
258 out = func(*args,**kw) # one last time
258 out = func(*args,**kw) # one last time
259 tot_time = clock()-start
259 tot_time = clock()-start
260 av_time = tot_time / reps
260 av_time = tot_time / reps
261 return tot_time,av_time,out
261 return tot_time,av_time,out
262
262
263 def timings(reps,func,*args,**kw):
263 def timings(reps,func,*args,**kw):
264 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
264 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
265
265
266 Execute a function reps times, return a tuple with the elapsed total CPU
266 Execute a function reps times, return a tuple with the elapsed total CPU
267 time in seconds and the time per call. These are just the first two values
267 time in seconds and the time per call. These are just the first two values
268 in timings_out()."""
268 in timings_out()."""
269
269
270 return timings_out(reps,func,*args,**kw)[0:2]
270 return timings_out(reps,func,*args,**kw)[0:2]
271
271
272 def timing(func,*args,**kw):
272 def timing(func,*args,**kw):
273 """timing(func,*args,**kw) -> t_total
273 """timing(func,*args,**kw) -> t_total
274
274
275 Execute a function once, return the elapsed total CPU time in
275 Execute a function once, return the elapsed total CPU time in
276 seconds. This is just the first value in timings_out()."""
276 seconds. This is just the first value in timings_out()."""
277
277
278 return timings_out(1,func,*args,**kw)[0]
278 return timings_out(1,func,*args,**kw)[0]
279
279
280 #****************************************************************************
280 #****************************************************************************
281 # file and system
281 # file and system
282
282
283 def arg_split(s,posix=False):
283 def arg_split(s,posix=False):
284 """Split a command line's arguments in a shell-like manner.
284 """Split a command line's arguments in a shell-like manner.
285
285
286 This is a modified version of the standard library's shlex.split()
286 This is a modified version of the standard library's shlex.split()
287 function, but with a default of posix=False for splitting, so that quotes
287 function, but with a default of posix=False for splitting, so that quotes
288 in inputs are respected."""
288 in inputs are respected."""
289
289
290 # XXX - there may be unicode-related problems here!!! I'm not sure that
290 # XXX - there may be unicode-related problems here!!! I'm not sure that
291 # shlex is truly unicode-safe, so it might be necessary to do
291 # shlex is truly unicode-safe, so it might be necessary to do
292 #
292 #
293 # s = s.encode(sys.stdin.encoding)
293 # s = s.encode(sys.stdin.encoding)
294 #
294 #
295 # first, to ensure that shlex gets a normal string. Input from anyone who
295 # first, to ensure that shlex gets a normal string. Input from anyone who
296 # knows more about unicode and shlex than I would be good to have here...
296 # knows more about unicode and shlex than I would be good to have here...
297 lex = shlex.shlex(s, posix=posix)
297 lex = shlex.shlex(s, posix=posix)
298 lex.whitespace_split = True
298 lex.whitespace_split = True
299 return list(lex)
299 return list(lex)
300
300
301 def system(cmd,verbose=0,debug=0,header=''):
301 def system(cmd,verbose=0,debug=0,header=''):
302 """Execute a system command, return its exit status.
302 """Execute a system command, return its exit status.
303
303
304 Options:
304 Options:
305
305
306 - verbose (0): print the command to be executed.
306 - verbose (0): print the command to be executed.
307
307
308 - debug (0): only print, do not actually execute.
308 - debug (0): only print, do not actually execute.
309
309
310 - header (''): Header to print on screen prior to the executed command (it
310 - header (''): Header to print on screen prior to the executed command (it
311 is only prepended to the command, no newlines are added).
311 is only prepended to the command, no newlines are added).
312
312
313 Note: a stateful version of this function is available through the
313 Note: a stateful version of this function is available through the
314 SystemExec class."""
314 SystemExec class."""
315
315
316 stat = 0
316 stat = 0
317 if verbose or debug: print header+cmd
317 if verbose or debug: print header+cmd
318 sys.stdout.flush()
318 sys.stdout.flush()
319 if not debug: stat = os.system(cmd)
319 if not debug: stat = os.system(cmd)
320 return stat
320 return stat
321
321
322 def abbrev_cwd():
322 def abbrev_cwd():
323 """ Return abbreviated version of cwd, e.g. d:mydir """
323 """ Return abbreviated version of cwd, e.g. d:mydir """
324 cwd = os.getcwd().replace('\\','/')
324 cwd = os.getcwd().replace('\\','/')
325 drivepart = ''
325 drivepart = ''
326 tail = cwd
326 tail = cwd
327 if sys.platform == 'win32':
327 if sys.platform == 'win32':
328 if len(cwd) < 4:
328 if len(cwd) < 4:
329 return cwd
329 return cwd
330 drivepart,tail = os.path.splitdrive(cwd)
330 drivepart,tail = os.path.splitdrive(cwd)
331
331
332
332
333 parts = tail.split('/')
333 parts = tail.split('/')
334 if len(parts) > 2:
334 if len(parts) > 2:
335 tail = '/'.join(parts[-2:])
335 tail = '/'.join(parts[-2:])
336
336
337 return (drivepart + (
337 return (drivepart + (
338 cwd == '/' and '/' or tail))
338 cwd == '/' and '/' or tail))
339
339
340
340
341 # This function is used by ipython in a lot of places to make system calls.
341 # This function is used by ipython in a lot of places to make system calls.
342 # We need it to be slightly different under win32, due to the vagaries of
342 # We need it to be slightly different under win32, due to the vagaries of
343 # 'network shares'. A win32 override is below.
343 # 'network shares'. A win32 override is below.
344
344
345 def shell(cmd,verbose=0,debug=0,header=''):
345 def shell(cmd,verbose=0,debug=0,header=''):
346 """Execute a command in the system shell, always return None.
346 """Execute a command in the system shell, always return None.
347
347
348 Options:
348 Options:
349
349
350 - verbose (0): print the command to be executed.
350 - verbose (0): print the command to be executed.
351
351
352 - debug (0): only print, do not actually execute.
352 - debug (0): only print, do not actually execute.
353
353
354 - header (''): Header to print on screen prior to the executed command (it
354 - header (''): Header to print on screen prior to the executed command (it
355 is only prepended to the command, no newlines are added).
355 is only prepended to the command, no newlines are added).
356
356
357 Note: this is similar to genutils.system(), but it returns None so it can
357 Note: this is similar to genutils.system(), but it returns None so it can
358 be conveniently used in interactive loops without getting the return value
358 be conveniently used in interactive loops without getting the return value
359 (typically 0) printed many times."""
359 (typically 0) printed many times."""
360
360
361 stat = 0
361 stat = 0
362 if verbose or debug: print header+cmd
362 if verbose or debug: print header+cmd
363 # flush stdout so we don't mangle python's buffering
363 # flush stdout so we don't mangle python's buffering
364 sys.stdout.flush()
364 sys.stdout.flush()
365
365
366 if not debug:
366 if not debug:
367 platutils.set_term_title("IPy " + cmd)
367 platutils.set_term_title("IPy " + cmd)
368 os.system(cmd)
368 os.system(cmd)
369 platutils.set_term_title("IPy " + abbrev_cwd())
369 platutils.set_term_title("IPy " + abbrev_cwd())
370
370
371 # override shell() for win32 to deal with network shares
371 # override shell() for win32 to deal with network shares
372 if os.name in ('nt','dos'):
372 if os.name in ('nt','dos'):
373
373
374 shell_ori = shell
374 shell_ori = shell
375
375
376 def shell(cmd,verbose=0,debug=0,header=''):
376 def shell(cmd,verbose=0,debug=0,header=''):
377 if os.getcwd().startswith(r"\\"):
377 if os.getcwd().startswith(r"\\"):
378 path = os.getcwd()
378 path = os.getcwd()
379 # change to c drive (cannot be on UNC-share when issuing os.system,
379 # change to c drive (cannot be on UNC-share when issuing os.system,
380 # as cmd.exe cannot handle UNC addresses)
380 # as cmd.exe cannot handle UNC addresses)
381 os.chdir("c:")
381 os.chdir("c:")
382 # issue pushd to the UNC-share and then run the command
382 # issue pushd to the UNC-share and then run the command
383 try:
383 try:
384 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
384 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
385 finally:
385 finally:
386 os.chdir(path)
386 os.chdir(path)
387 else:
387 else:
388 shell_ori(cmd,verbose,debug,header)
388 shell_ori(cmd,verbose,debug,header)
389
389
390 shell.__doc__ = shell_ori.__doc__
390 shell.__doc__ = shell_ori.__doc__
391
391
392 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
392 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
393 """Dummy substitute for perl's backquotes.
393 """Dummy substitute for perl's backquotes.
394
394
395 Executes a command and returns the output.
395 Executes a command and returns the output.
396
396
397 Accepts the same arguments as system(), plus:
397 Accepts the same arguments as system(), plus:
398
398
399 - split(0): if true, the output is returned as a list split on newlines.
399 - split(0): if true, the output is returned as a list split on newlines.
400
400
401 Note: a stateful version of this function is available through the
401 Note: a stateful version of this function is available through the
402 SystemExec class.
402 SystemExec class.
403
403
404 This is pretty much deprecated and rarely used,
404 This is pretty much deprecated and rarely used,
405 genutils.getoutputerror may be what you need.
405 genutils.getoutputerror may be what you need.
406
406
407 """
407 """
408
408
409 if verbose or debug: print header+cmd
409 if verbose or debug: print header+cmd
410 if not debug:
410 if not debug:
411 output = os.popen(cmd).read()
411 output = os.popen(cmd).read()
412 # stipping last \n is here for backwards compat.
412 # stipping last \n is here for backwards compat.
413 if output.endswith('\n'):
413 if output.endswith('\n'):
414 output = output[:-1]
414 output = output[:-1]
415 if split:
415 if split:
416 return output.split('\n')
416 return output.split('\n')
417 else:
417 else:
418 return output
418 return output
419
419
420 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
420 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
421 """Return (standard output,standard error) of executing cmd in a shell.
421 """Return (standard output,standard error) of executing cmd in a shell.
422
422
423 Accepts the same arguments as system(), plus:
423 Accepts the same arguments as system(), plus:
424
424
425 - split(0): if true, each of stdout/err is returned as a list split on
425 - split(0): if true, each of stdout/err is returned as a list split on
426 newlines.
426 newlines.
427
427
428 Note: a stateful version of this function is available through the
428 Note: a stateful version of this function is available through the
429 SystemExec class."""
429 SystemExec class."""
430
430
431 if verbose or debug: print header+cmd
431 if verbose or debug: print header+cmd
432 if not cmd:
432 if not cmd:
433 if split:
433 if split:
434 return [],[]
434 return [],[]
435 else:
435 else:
436 return '',''
436 return '',''
437 if not debug:
437 if not debug:
438 pin,pout,perr = os.popen3(cmd)
438 pin,pout,perr = os.popen3(cmd)
439 tout = pout.read().rstrip()
439 tout = pout.read().rstrip()
440 terr = perr.read().rstrip()
440 terr = perr.read().rstrip()
441 pin.close()
441 pin.close()
442 pout.close()
442 pout.close()
443 perr.close()
443 perr.close()
444 if split:
444 if split:
445 return tout.split('\n'),terr.split('\n')
445 return tout.split('\n'),terr.split('\n')
446 else:
446 else:
447 return tout,terr
447 return tout,terr
448
448
449 # for compatibility with older naming conventions
449 # for compatibility with older naming conventions
450 xsys = system
450 xsys = system
451 bq = getoutput
451 bq = getoutput
452
452
453 class SystemExec:
453 class SystemExec:
454 """Access the system and getoutput functions through a stateful interface.
454 """Access the system and getoutput functions through a stateful interface.
455
455
456 Note: here we refer to the system and getoutput functions from this
456 Note: here we refer to the system and getoutput functions from this
457 library, not the ones from the standard python library.
457 library, not the ones from the standard python library.
458
458
459 This class offers the system and getoutput functions as methods, but the
459 This class offers the system and getoutput functions as methods, but the
460 verbose, debug and header parameters can be set for the instance (at
460 verbose, debug and header parameters can be set for the instance (at
461 creation time or later) so that they don't need to be specified on each
461 creation time or later) so that they don't need to be specified on each
462 call.
462 call.
463
463
464 For efficiency reasons, there's no way to override the parameters on a
464 For efficiency reasons, there's no way to override the parameters on a
465 per-call basis other than by setting instance attributes. If you need
465 per-call basis other than by setting instance attributes. If you need
466 local overrides, it's best to directly call system() or getoutput().
466 local overrides, it's best to directly call system() or getoutput().
467
467
468 The following names are provided as alternate options:
468 The following names are provided as alternate options:
469 - xsys: alias to system
469 - xsys: alias to system
470 - bq: alias to getoutput
470 - bq: alias to getoutput
471
471
472 An instance can then be created as:
472 An instance can then be created as:
473 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
473 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
474 """
474 """
475
475
476 def __init__(self,verbose=0,debug=0,header='',split=0):
476 def __init__(self,verbose=0,debug=0,header='',split=0):
477 """Specify the instance's values for verbose, debug and header."""
477 """Specify the instance's values for verbose, debug and header."""
478 setattr_list(self,'verbose debug header split')
478 setattr_list(self,'verbose debug header split')
479
479
480 def system(self,cmd):
480 def system(self,cmd):
481 """Stateful interface to system(), with the same keyword parameters."""
481 """Stateful interface to system(), with the same keyword parameters."""
482
482
483 system(cmd,self.verbose,self.debug,self.header)
483 system(cmd,self.verbose,self.debug,self.header)
484
484
485 def shell(self,cmd):
485 def shell(self,cmd):
486 """Stateful interface to shell(), with the same keyword parameters."""
486 """Stateful interface to shell(), with the same keyword parameters."""
487
487
488 shell(cmd,self.verbose,self.debug,self.header)
488 shell(cmd,self.verbose,self.debug,self.header)
489
489
490 xsys = system # alias
490 xsys = system # alias
491
491
492 def getoutput(self,cmd):
492 def getoutput(self,cmd):
493 """Stateful interface to getoutput()."""
493 """Stateful interface to getoutput()."""
494
494
495 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
495 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
496
496
497 def getoutputerror(self,cmd):
497 def getoutputerror(self,cmd):
498 """Stateful interface to getoutputerror()."""
498 """Stateful interface to getoutputerror()."""
499
499
500 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
500 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
501
501
502 bq = getoutput # alias
502 bq = getoutput # alias
503
503
504 #-----------------------------------------------------------------------------
504 #-----------------------------------------------------------------------------
505 def mutex_opts(dict,ex_op):
505 def mutex_opts(dict,ex_op):
506 """Check for presence of mutually exclusive keys in a dict.
506 """Check for presence of mutually exclusive keys in a dict.
507
507
508 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
508 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
509 for op1,op2 in ex_op:
509 for op1,op2 in ex_op:
510 if op1 in dict and op2 in dict:
510 if op1 in dict and op2 in dict:
511 raise ValueError,'\n*** ERROR in Arguments *** '\
511 raise ValueError,'\n*** ERROR in Arguments *** '\
512 'Options '+op1+' and '+op2+' are mutually exclusive.'
512 'Options '+op1+' and '+op2+' are mutually exclusive.'
513
513
514 #-----------------------------------------------------------------------------
514 #-----------------------------------------------------------------------------
515 def get_py_filename(name):
515 def get_py_filename(name):
516 """Return a valid python filename in the current directory.
516 """Return a valid python filename in the current directory.
517
517
518 If the given name is not a file, it adds '.py' and searches again.
518 If the given name is not a file, it adds '.py' and searches again.
519 Raises IOError with an informative message if the file isn't found."""
519 Raises IOError with an informative message if the file isn't found."""
520
520
521 name = os.path.expanduser(name)
521 name = os.path.expanduser(name)
522 if not os.path.isfile(name) and not name.endswith('.py'):
522 if not os.path.isfile(name) and not name.endswith('.py'):
523 name += '.py'
523 name += '.py'
524 if os.path.isfile(name):
524 if os.path.isfile(name):
525 return name
525 return name
526 else:
526 else:
527 raise IOError,'File `%s` not found.' % name
527 raise IOError,'File `%s` not found.' % name
528
528
529 #-----------------------------------------------------------------------------
529 #-----------------------------------------------------------------------------
530 def filefind(fname,alt_dirs = None):
530 def filefind(fname,alt_dirs = None):
531 """Return the given filename either in the current directory, if it
531 """Return the given filename either in the current directory, if it
532 exists, or in a specified list of directories.
532 exists, or in a specified list of directories.
533
533
534 ~ expansion is done on all file and directory names.
534 ~ expansion is done on all file and directory names.
535
535
536 Upon an unsuccessful search, raise an IOError exception."""
536 Upon an unsuccessful search, raise an IOError exception."""
537
537
538 if alt_dirs is None:
538 if alt_dirs is None:
539 try:
539 try:
540 alt_dirs = get_home_dir()
540 alt_dirs = get_home_dir()
541 except HomeDirError:
541 except HomeDirError:
542 alt_dirs = os.getcwd()
542 alt_dirs = os.getcwd()
543 search = [fname] + list_strings(alt_dirs)
543 search = [fname] + list_strings(alt_dirs)
544 search = map(os.path.expanduser,search)
544 search = map(os.path.expanduser,search)
545 #print 'search list for',fname,'list:',search # dbg
545 #print 'search list for',fname,'list:',search # dbg
546 fname = search[0]
546 fname = search[0]
547 if os.path.isfile(fname):
547 if os.path.isfile(fname):
548 return fname
548 return fname
549 for direc in search[1:]:
549 for direc in search[1:]:
550 testname = os.path.join(direc,fname)
550 testname = os.path.join(direc,fname)
551 #print 'testname',testname # dbg
551 #print 'testname',testname # dbg
552 if os.path.isfile(testname):
552 if os.path.isfile(testname):
553 return testname
553 return testname
554 raise IOError,'File' + `fname` + \
554 raise IOError,'File' + `fname` + \
555 ' not found in current or supplied directories:' + `alt_dirs`
555 ' not found in current or supplied directories:' + `alt_dirs`
556
556
557 #----------------------------------------------------------------------------
557 #----------------------------------------------------------------------------
558 def file_read(filename):
558 def file_read(filename):
559 """Read a file and close it. Returns the file source."""
559 """Read a file and close it. Returns the file source."""
560 fobj = open(filename,'r');
560 fobj = open(filename,'r');
561 source = fobj.read();
561 source = fobj.read();
562 fobj.close()
562 fobj.close()
563 return source
563 return source
564
564
565 def file_readlines(filename):
565 def file_readlines(filename):
566 """Read a file and close it. Returns the file source using readlines()."""
566 """Read a file and close it. Returns the file source using readlines()."""
567 fobj = open(filename,'r');
567 fobj = open(filename,'r');
568 lines = fobj.readlines();
568 lines = fobj.readlines();
569 fobj.close()
569 fobj.close()
570 return lines
570 return lines
571
571
572 #----------------------------------------------------------------------------
572 #----------------------------------------------------------------------------
573 def target_outdated(target,deps):
573 def target_outdated(target,deps):
574 """Determine whether a target is out of date.
574 """Determine whether a target is out of date.
575
575
576 target_outdated(target,deps) -> 1/0
576 target_outdated(target,deps) -> 1/0
577
577
578 deps: list of filenames which MUST exist.
578 deps: list of filenames which MUST exist.
579 target: single filename which may or may not exist.
579 target: single filename which may or may not exist.
580
580
581 If target doesn't exist or is older than any file listed in deps, return
581 If target doesn't exist or is older than any file listed in deps, return
582 true, otherwise return false.
582 true, otherwise return false.
583 """
583 """
584 try:
584 try:
585 target_time = os.path.getmtime(target)
585 target_time = os.path.getmtime(target)
586 except os.error:
586 except os.error:
587 return 1
587 return 1
588 for dep in deps:
588 for dep in deps:
589 dep_time = os.path.getmtime(dep)
589 dep_time = os.path.getmtime(dep)
590 if dep_time > target_time:
590 if dep_time > target_time:
591 #print "For target",target,"Dep failed:",dep # dbg
591 #print "For target",target,"Dep failed:",dep # dbg
592 #print "times (dep,tar):",dep_time,target_time # dbg
592 #print "times (dep,tar):",dep_time,target_time # dbg
593 return 1
593 return 1
594 return 0
594 return 0
595
595
596 #-----------------------------------------------------------------------------
596 #-----------------------------------------------------------------------------
597 def target_update(target,deps,cmd):
597 def target_update(target,deps,cmd):
598 """Update a target with a given command given a list of dependencies.
598 """Update a target with a given command given a list of dependencies.
599
599
600 target_update(target,deps,cmd) -> runs cmd if target is outdated.
600 target_update(target,deps,cmd) -> runs cmd if target is outdated.
601
601
602 This is just a wrapper around target_outdated() which calls the given
602 This is just a wrapper around target_outdated() which calls the given
603 command if target is outdated."""
603 command if target is outdated."""
604
604
605 if target_outdated(target,deps):
605 if target_outdated(target,deps):
606 xsys(cmd)
606 xsys(cmd)
607
607
608 #----------------------------------------------------------------------------
608 #----------------------------------------------------------------------------
609 def unquote_ends(istr):
609 def unquote_ends(istr):
610 """Remove a single pair of quotes from the endpoints of a string."""
610 """Remove a single pair of quotes from the endpoints of a string."""
611
611
612 if not istr:
612 if not istr:
613 return istr
613 return istr
614 if (istr[0]=="'" and istr[-1]=="'") or \
614 if (istr[0]=="'" and istr[-1]=="'") or \
615 (istr[0]=='"' and istr[-1]=='"'):
615 (istr[0]=='"' and istr[-1]=='"'):
616 return istr[1:-1]
616 return istr[1:-1]
617 else:
617 else:
618 return istr
618 return istr
619
619
620 #----------------------------------------------------------------------------
620 #----------------------------------------------------------------------------
621 def process_cmdline(argv,names=[],defaults={},usage=''):
621 def process_cmdline(argv,names=[],defaults={},usage=''):
622 """ Process command-line options and arguments.
622 """ Process command-line options and arguments.
623
623
624 Arguments:
624 Arguments:
625
625
626 - argv: list of arguments, typically sys.argv.
626 - argv: list of arguments, typically sys.argv.
627
627
628 - names: list of option names. See DPyGetOpt docs for details on options
628 - names: list of option names. See DPyGetOpt docs for details on options
629 syntax.
629 syntax.
630
630
631 - defaults: dict of default values.
631 - defaults: dict of default values.
632
632
633 - usage: optional usage notice to print if a wrong argument is passed.
633 - usage: optional usage notice to print if a wrong argument is passed.
634
634
635 Return a dict of options and a list of free arguments."""
635 Return a dict of options and a list of free arguments."""
636
636
637 getopt = DPyGetOpt.DPyGetOpt()
637 getopt = DPyGetOpt.DPyGetOpt()
638 getopt.setIgnoreCase(0)
638 getopt.setIgnoreCase(0)
639 getopt.parseConfiguration(names)
639 getopt.parseConfiguration(names)
640
640
641 try:
641 try:
642 getopt.processArguments(argv)
642 getopt.processArguments(argv)
643 except DPyGetOpt.ArgumentError, exc:
643 except DPyGetOpt.ArgumentError, exc:
644 print usage
644 print usage
645 warn('"%s"' % exc,level=4)
645 warn('"%s"' % exc,level=4)
646
646
647 defaults.update(getopt.optionValues)
647 defaults.update(getopt.optionValues)
648 args = getopt.freeValues
648 args = getopt.freeValues
649
649
650 return defaults,args
650 return defaults,args
651
651
652 #----------------------------------------------------------------------------
652 #----------------------------------------------------------------------------
653 def optstr2types(ostr):
653 def optstr2types(ostr):
654 """Convert a string of option names to a dict of type mappings.
654 """Convert a string of option names to a dict of type mappings.
655
655
656 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
656 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
657
657
658 This is used to get the types of all the options in a string formatted
658 This is used to get the types of all the options in a string formatted
659 with the conventions of DPyGetOpt. The 'type' None is used for options
659 with the conventions of DPyGetOpt. The 'type' None is used for options
660 which are strings (they need no further conversion). This function's main
660 which are strings (they need no further conversion). This function's main
661 use is to get a typemap for use with read_dict().
661 use is to get a typemap for use with read_dict().
662 """
662 """
663
663
664 typeconv = {None:'',int:'',float:''}
664 typeconv = {None:'',int:'',float:''}
665 typemap = {'s':None,'i':int,'f':float}
665 typemap = {'s':None,'i':int,'f':float}
666 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
666 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
667
667
668 for w in ostr.split():
668 for w in ostr.split():
669 oname,alias,otype = opt_re.match(w).groups()
669 oname,alias,otype = opt_re.match(w).groups()
670 if otype == '' or alias == '!': # simple switches are integers too
670 if otype == '' or alias == '!': # simple switches are integers too
671 otype = 'i'
671 otype = 'i'
672 typeconv[typemap[otype]] += oname + ' '
672 typeconv[typemap[otype]] += oname + ' '
673 return typeconv
673 return typeconv
674
674
675 #----------------------------------------------------------------------------
675 #----------------------------------------------------------------------------
676 def read_dict(filename,type_conv=None,**opt):
676 def read_dict(filename,type_conv=None,**opt):
677 r"""Read a dictionary of key=value pairs from an input file, optionally
677 r"""Read a dictionary of key=value pairs from an input file, optionally
678 performing conversions on the resulting values.
678 performing conversions on the resulting values.
679
679
680 read_dict(filename,type_conv,**opt) -> dict
680 read_dict(filename,type_conv,**opt) -> dict
681
681
682 Only one value per line is accepted, the format should be
682 Only one value per line is accepted, the format should be
683 # optional comments are ignored
683 # optional comments are ignored
684 key value\n
684 key value\n
685
685
686 Args:
686 Args:
687
687
688 - type_conv: A dictionary specifying which keys need to be converted to
688 - type_conv: A dictionary specifying which keys need to be converted to
689 which types. By default all keys are read as strings. This dictionary
689 which types. By default all keys are read as strings. This dictionary
690 should have as its keys valid conversion functions for strings
690 should have as its keys valid conversion functions for strings
691 (int,long,float,complex, or your own). The value for each key
691 (int,long,float,complex, or your own). The value for each key
692 (converter) should be a whitespace separated string containing the names
692 (converter) should be a whitespace separated string containing the names
693 of all the entries in the file to be converted using that function. For
693 of all the entries in the file to be converted using that function. For
694 keys to be left alone, use None as the conversion function (only needed
694 keys to be left alone, use None as the conversion function (only needed
695 with purge=1, see below).
695 with purge=1, see below).
696
696
697 - opt: dictionary with extra options as below (default in parens)
697 - opt: dictionary with extra options as below (default in parens)
698
698
699 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
699 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
700 of the dictionary to be returned. If purge is going to be used, the
700 of the dictionary to be returned. If purge is going to be used, the
701 set of keys to be left as strings also has to be explicitly specified
701 set of keys to be left as strings also has to be explicitly specified
702 using the (non-existent) conversion function None.
702 using the (non-existent) conversion function None.
703
703
704 fs(None): field separator. This is the key/value separator to be used
704 fs(None): field separator. This is the key/value separator to be used
705 when parsing the file. The None default means any whitespace [behavior
705 when parsing the file. The None default means any whitespace [behavior
706 of string.split()].
706 of string.split()].
707
707
708 strip(0): if 1, strip string values of leading/trailinig whitespace.
708 strip(0): if 1, strip string values of leading/trailinig whitespace.
709
709
710 warn(1): warning level if requested keys are not found in file.
710 warn(1): warning level if requested keys are not found in file.
711 - 0: silently ignore.
711 - 0: silently ignore.
712 - 1: inform but proceed.
712 - 1: inform but proceed.
713 - 2: raise KeyError exception.
713 - 2: raise KeyError exception.
714
714
715 no_empty(0): if 1, remove keys with whitespace strings as a value.
715 no_empty(0): if 1, remove keys with whitespace strings as a value.
716
716
717 unique([]): list of keys (or space separated string) which can't be
717 unique([]): list of keys (or space separated string) which can't be
718 repeated. If one such key is found in the file, each new instance
718 repeated. If one such key is found in the file, each new instance
719 overwrites the previous one. For keys not listed here, the behavior is
719 overwrites the previous one. For keys not listed here, the behavior is
720 to make a list of all appearances.
720 to make a list of all appearances.
721
721
722 Example:
722 Example:
723
723
724 If the input file test.ini contains (we put it in a string to keep the test
724 If the input file test.ini contains (we put it in a string to keep the test
725 self-contained):
725 self-contained):
726
726
727 >>> test_ini = '''\
727 >>> test_ini = '''\
728 ... i 3
728 ... i 3
729 ... x 4.5
729 ... x 4.5
730 ... y 5.5
730 ... y 5.5
731 ... s hi ho'''
731 ... s hi ho'''
732
732
733 Then we can use it as follows:
733 Then we can use it as follows:
734 >>> type_conv={int:'i',float:'x',None:'s'}
734 >>> type_conv={int:'i',float:'x',None:'s'}
735
735
736 >>> d = read_dict(test_ini)
736 >>> d = read_dict(test_ini)
737
737
738 >>> sorted(d.items())
738 >>> sorted(d.items())
739 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
739 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
740
740
741 >>> d = read_dict(test_ini,type_conv)
741 >>> d = read_dict(test_ini,type_conv)
742
742
743 >>> sorted(d.items())
743 >>> sorted(d.items())
744 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
744 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
745
745
746 >>> d = read_dict(test_ini,type_conv,purge=True)
746 >>> d = read_dict(test_ini,type_conv,purge=True)
747
747
748 >>> sorted(d.items())
748 >>> sorted(d.items())
749 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
749 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
750 """
750 """
751
751
752 # starting config
752 # starting config
753 opt.setdefault('purge',0)
753 opt.setdefault('purge',0)
754 opt.setdefault('fs',None) # field sep defaults to any whitespace
754 opt.setdefault('fs',None) # field sep defaults to any whitespace
755 opt.setdefault('strip',0)
755 opt.setdefault('strip',0)
756 opt.setdefault('warn',1)
756 opt.setdefault('warn',1)
757 opt.setdefault('no_empty',0)
757 opt.setdefault('no_empty',0)
758 opt.setdefault('unique','')
758 opt.setdefault('unique','')
759 if type(opt['unique']) in StringTypes:
759 if type(opt['unique']) in StringTypes:
760 unique_keys = qw(opt['unique'])
760 unique_keys = qw(opt['unique'])
761 elif type(opt['unique']) in (types.TupleType,types.ListType):
761 elif type(opt['unique']) in (types.TupleType,types.ListType):
762 unique_keys = opt['unique']
762 unique_keys = opt['unique']
763 else:
763 else:
764 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
764 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
765
765
766 dict = {}
766 dict = {}
767
767
768 # first read in table of values as strings
768 # first read in table of values as strings
769 if '\n' in filename:
769 if '\n' in filename:
770 lines = filename.splitlines()
770 lines = filename.splitlines()
771 file = None
771 file = None
772 else:
772 else:
773 file = open(filename,'r')
773 file = open(filename,'r')
774 lines = file.readlines()
774 lines = file.readlines()
775 for line in lines:
775 for line in lines:
776 line = line.strip()
776 line = line.strip()
777 if len(line) and line[0]=='#': continue
777 if len(line) and line[0]=='#': continue
778 if len(line)>0:
778 if len(line)>0:
779 lsplit = line.split(opt['fs'],1)
779 lsplit = line.split(opt['fs'],1)
780 try:
780 try:
781 key,val = lsplit
781 key,val = lsplit
782 except ValueError:
782 except ValueError:
783 key,val = lsplit[0],''
783 key,val = lsplit[0],''
784 key = key.strip()
784 key = key.strip()
785 if opt['strip']: val = val.strip()
785 if opt['strip']: val = val.strip()
786 if val == "''" or val == '""': val = ''
786 if val == "''" or val == '""': val = ''
787 if opt['no_empty'] and (val=='' or val.isspace()):
787 if opt['no_empty'] and (val=='' or val.isspace()):
788 continue
788 continue
789 # if a key is found more than once in the file, build a list
789 # if a key is found more than once in the file, build a list
790 # unless it's in the 'unique' list. In that case, last found in file
790 # unless it's in the 'unique' list. In that case, last found in file
791 # takes precedence. User beware.
791 # takes precedence. User beware.
792 try:
792 try:
793 if dict[key] and key in unique_keys:
793 if dict[key] and key in unique_keys:
794 dict[key] = val
794 dict[key] = val
795 elif type(dict[key]) is types.ListType:
795 elif type(dict[key]) is types.ListType:
796 dict[key].append(val)
796 dict[key].append(val)
797 else:
797 else:
798 dict[key] = [dict[key],val]
798 dict[key] = [dict[key],val]
799 except KeyError:
799 except KeyError:
800 dict[key] = val
800 dict[key] = val
801 # purge if requested
801 # purge if requested
802 if opt['purge']:
802 if opt['purge']:
803 accepted_keys = qwflat(type_conv.values())
803 accepted_keys = qwflat(type_conv.values())
804 for key in dict.keys():
804 for key in dict.keys():
805 if key in accepted_keys: continue
805 if key in accepted_keys: continue
806 del(dict[key])
806 del(dict[key])
807 # now convert if requested
807 # now convert if requested
808 if type_conv==None: return dict
808 if type_conv==None: return dict
809 conversions = type_conv.keys()
809 conversions = type_conv.keys()
810 try: conversions.remove(None)
810 try: conversions.remove(None)
811 except: pass
811 except: pass
812 for convert in conversions:
812 for convert in conversions:
813 for val in qw(type_conv[convert]):
813 for val in qw(type_conv[convert]):
814 try:
814 try:
815 dict[val] = convert(dict[val])
815 dict[val] = convert(dict[val])
816 except KeyError,e:
816 except KeyError,e:
817 if opt['warn'] == 0:
817 if opt['warn'] == 0:
818 pass
818 pass
819 elif opt['warn'] == 1:
819 elif opt['warn'] == 1:
820 print >>sys.stderr, 'Warning: key',val,\
820 print >>sys.stderr, 'Warning: key',val,\
821 'not found in file',filename
821 'not found in file',filename
822 elif opt['warn'] == 2:
822 elif opt['warn'] == 2:
823 raise KeyError,e
823 raise KeyError,e
824 else:
824 else:
825 raise ValueError,'Warning level must be 0,1 or 2'
825 raise ValueError,'Warning level must be 0,1 or 2'
826
826
827 return dict
827 return dict
828
828
829 #----------------------------------------------------------------------------
829 #----------------------------------------------------------------------------
830 def flag_calls(func):
830 def flag_calls(func):
831 """Wrap a function to detect and flag when it gets called.
831 """Wrap a function to detect and flag when it gets called.
832
832
833 This is a decorator which takes a function and wraps it in a function with
833 This is a decorator which takes a function and wraps it in a function with
834 a 'called' attribute. wrapper.called is initialized to False.
834 a 'called' attribute. wrapper.called is initialized to False.
835
835
836 The wrapper.called attribute is set to False right before each call to the
836 The wrapper.called attribute is set to False right before each call to the
837 wrapped function, so if the call fails it remains False. After the call
837 wrapped function, so if the call fails it remains False. After the call
838 completes, wrapper.called is set to True and the output is returned.
838 completes, wrapper.called is set to True and the output is returned.
839
839
840 Testing for truth in wrapper.called allows you to determine if a call to
840 Testing for truth in wrapper.called allows you to determine if a call to
841 func() was attempted and succeeded."""
841 func() was attempted and succeeded."""
842
842
843 def wrapper(*args,**kw):
843 def wrapper(*args,**kw):
844 wrapper.called = False
844 wrapper.called = False
845 out = func(*args,**kw)
845 out = func(*args,**kw)
846 wrapper.called = True
846 wrapper.called = True
847 return out
847 return out
848
848
849 wrapper.called = False
849 wrapper.called = False
850 wrapper.__doc__ = func.__doc__
850 wrapper.__doc__ = func.__doc__
851 return wrapper
851 return wrapper
852
852
853 #----------------------------------------------------------------------------
853 #----------------------------------------------------------------------------
854 def dhook_wrap(func,*a,**k):
854 def dhook_wrap(func,*a,**k):
855 """Wrap a function call in a sys.displayhook controller.
855 """Wrap a function call in a sys.displayhook controller.
856
856
857 Returns a wrapper around func which calls func, with all its arguments and
857 Returns a wrapper around func which calls func, with all its arguments and
858 keywords unmodified, using the default sys.displayhook. Since IPython
858 keywords unmodified, using the default sys.displayhook. Since IPython
859 modifies sys.displayhook, it breaks the behavior of certain systems that
859 modifies sys.displayhook, it breaks the behavior of certain systems that
860 rely on the default behavior, notably doctest.
860 rely on the default behavior, notably doctest.
861 """
861 """
862
862
863 def f(*a,**k):
863 def f(*a,**k):
864
864
865 dhook_s = sys.displayhook
865 dhook_s = sys.displayhook
866 sys.displayhook = sys.__displayhook__
866 sys.displayhook = sys.__displayhook__
867 try:
867 try:
868 out = func(*a,**k)
868 out = func(*a,**k)
869 finally:
869 finally:
870 sys.displayhook = dhook_s
870 sys.displayhook = dhook_s
871
871
872 return out
872 return out
873
873
874 f.__doc__ = func.__doc__
874 f.__doc__ = func.__doc__
875 return f
875 return f
876
876
877 #----------------------------------------------------------------------------
877 #----------------------------------------------------------------------------
878 def doctest_reload():
878 def doctest_reload():
879 """Properly reload doctest to reuse it interactively.
879 """Properly reload doctest to reuse it interactively.
880
880
881 This routine:
881 This routine:
882
882
883 - reloads doctest
883 - reloads doctest
884
884
885 - resets its global 'master' attribute to None, so that multiple uses of
885 - resets its global 'master' attribute to None, so that multiple uses of
886 the module interactively don't produce cumulative reports.
886 the module interactively don't produce cumulative reports.
887
887
888 - Monkeypatches its core test runner method to protect it from IPython's
888 - Monkeypatches its core test runner method to protect it from IPython's
889 modified displayhook. Doctest expects the default displayhook behavior
889 modified displayhook. Doctest expects the default displayhook behavior
890 deep down, so our modification breaks it completely. For this reason, a
890 deep down, so our modification breaks it completely. For this reason, a
891 hard monkeypatch seems like a reasonable solution rather than asking
891 hard monkeypatch seems like a reasonable solution rather than asking
892 users to manually use a different doctest runner when under IPython."""
892 users to manually use a different doctest runner when under IPython."""
893
893
894 import doctest
894 import doctest
895 reload(doctest)
895 reload(doctest)
896 doctest.master=None
896 doctest.master=None
897
897
898 try:
898 try:
899 doctest.DocTestRunner
899 doctest.DocTestRunner
900 except AttributeError:
900 except AttributeError:
901 # This is only for python 2.3 compatibility, remove once we move to
901 # This is only for python 2.3 compatibility, remove once we move to
902 # 2.4 only.
902 # 2.4 only.
903 pass
903 pass
904 else:
904 else:
905 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
905 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
906
906
907 #----------------------------------------------------------------------------
907 #----------------------------------------------------------------------------
908 class HomeDirError(Error):
908 class HomeDirError(Error):
909 pass
909 pass
910
910
911 def get_home_dir():
911 def get_home_dir():
912 """Return the closest possible equivalent to a 'home' directory.
912 """Return the closest possible equivalent to a 'home' directory.
913
913
914 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
914 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
915
915
916 Currently only Posix and NT are implemented, a HomeDirError exception is
916 Currently only Posix and NT are implemented, a HomeDirError exception is
917 raised for all other OSes. """
917 raised for all other OSes. """
918
918
919 isdir = os.path.isdir
919 isdir = os.path.isdir
920 env = os.environ
920 env = os.environ
921
921
922 # first, check py2exe distribution root directory for _ipython.
922 # first, check py2exe distribution root directory for _ipython.
923 # This overrides all. Normally does not exist.
923 # This overrides all. Normally does not exist.
924
924
925 if hasattr(sys, "frozen"): #Is frozen by py2exe
925 if hasattr(sys, "frozen"): #Is frozen by py2exe
926 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
926 if '\\library.zip\\' in IPython.__file__.lower():#libraries compressed to zip-file
927 root, rest = IPython.__file__.lower().split('library.zip')
927 root, rest = IPython.__file__.lower().split('library.zip')
928 else:
928 else:
929 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
929 root=os.path.join(os.path.split(IPython.__file__)[0],"../../")
930 root=os.path.abspath(root).rstrip('\\')
930 root=os.path.abspath(root).rstrip('\\')
931 if isdir(os.path.join(root, '_ipython')):
931 if isdir(os.path.join(root, '_ipython')):
932 os.environ["IPYKITROOT"] = root
932 os.environ["IPYKITROOT"] = root
933 return root
933 return root
934 try:
934 try:
935 homedir = env['HOME']
935 homedir = env['HOME']
936 if not isdir(homedir):
936 if not isdir(homedir):
937 # in case a user stuck some string which does NOT resolve to a
937 # in case a user stuck some string which does NOT resolve to a
938 # valid path, it's as good as if we hadn't foud it
938 # valid path, it's as good as if we hadn't foud it
939 raise KeyError
939 raise KeyError
940 return homedir
940 return homedir
941 except KeyError:
941 except KeyError:
942 if os.name == 'posix':
942 if os.name == 'posix':
943 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
943 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
944 elif os.name == 'nt':
944 elif os.name == 'nt':
945 # For some strange reason, win9x returns 'nt' for os.name.
945 # For some strange reason, win9x returns 'nt' for os.name.
946 try:
946 try:
947 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
947 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
948 if not isdir(homedir):
948 if not isdir(homedir):
949 homedir = os.path.join(env['USERPROFILE'])
949 homedir = os.path.join(env['USERPROFILE'])
950 if not isdir(homedir):
950 if not isdir(homedir):
951 raise HomeDirError
951 raise HomeDirError
952 return homedir
952 return homedir
953 except KeyError:
953 except KeyError:
954 try:
954 try:
955 # Use the registry to get the 'My Documents' folder.
955 # Use the registry to get the 'My Documents' folder.
956 import _winreg as wreg
956 import _winreg as wreg
957 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
957 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
958 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
958 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
959 homedir = wreg.QueryValueEx(key,'Personal')[0]
959 homedir = wreg.QueryValueEx(key,'Personal')[0]
960 key.Close()
960 key.Close()
961 if not isdir(homedir):
961 if not isdir(homedir):
962 e = ('Invalid "Personal" folder registry key '
962 e = ('Invalid "Personal" folder registry key '
963 'typically "My Documents".\n'
963 'typically "My Documents".\n'
964 'Value: %s\n'
964 'Value: %s\n'
965 'This is not a valid directory on your system.' %
965 'This is not a valid directory on your system.' %
966 homedir)
966 homedir)
967 raise HomeDirError(e)
967 raise HomeDirError(e)
968 return homedir
968 return homedir
969 except HomeDirError:
969 except HomeDirError:
970 raise
970 raise
971 except:
971 except:
972 return 'C:\\'
972 return 'C:\\'
973 elif os.name == 'dos':
973 elif os.name == 'dos':
974 # Desperate, may do absurd things in classic MacOS. May work under DOS.
974 # Desperate, may do absurd things in classic MacOS. May work under DOS.
975 return 'C:\\'
975 return 'C:\\'
976 else:
976 else:
977 raise HomeDirError,'support for your operating system not implemented.'
977 raise HomeDirError,'support for your operating system not implemented.'
978
978
979
979
980 def get_ipython_dir():
980 def get_ipython_dir():
981 """Get the IPython directory for this platform and user.
981 """Get the IPython directory for this platform and user.
982
982
983 This uses the logic in `get_home_dir` to find the home directory
983 This uses the logic in `get_home_dir` to find the home directory
984 and the adds either .ipython or _ipython to the end of the path.
984 and the adds either .ipython or _ipython to the end of the path.
985 """
985 """
986 if os.name == 'posix':
986 if os.name == 'posix':
987 ipdir_def = '.ipython'
987 ipdir_def = '.ipython'
988 else:
988 else:
989 ipdir_def = '_ipython'
989 ipdir_def = '_ipython'
990 home_dir = get_home_dir()
990 home_dir = get_home_dir()
991 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
991 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
992 os.path.join(home_dir, ipdir_def)))
992 os.path.join(home_dir, ipdir_def)))
993 return ipdir.decode(sys.getfilesystemencoding())
993 return ipdir.decode(sys.getfilesystemencoding())
994
994
995 def get_security_dir():
995 def get_security_dir():
996 """Get the IPython security directory.
996 """Get the IPython security directory.
997
997
998 This directory is the default location for all security related files,
998 This directory is the default location for all security related files,
999 including SSL/TLS certificates and FURL files.
999 including SSL/TLS certificates and FURL files.
1000
1000
1001 If the directory does not exist, it is created with 0700 permissions.
1001 If the directory does not exist, it is created with 0700 permissions.
1002 If it exists, permissions are set to 0700.
1002 If it exists, permissions are set to 0700.
1003 """
1003 """
1004 security_dir = os.path.join(get_ipython_dir(), 'security')
1004 security_dir = os.path.join(get_ipython_dir(), 'security')
1005 if not os.path.isdir(security_dir):
1005 if not os.path.isdir(security_dir):
1006 os.mkdir(security_dir, 0700)
1006 os.mkdir(security_dir, 0700)
1007 else:
1007 else:
1008 os.chmod(security_dir, 0700)
1008 os.chmod(security_dir, 0700)
1009 return security_dir
1009 return security_dir
1010
1010
1011 def get_log_dir():
1012 """Get the IPython log directory.
1013
1014 If the log directory does not exist, it is created.
1015 """
1016 log_dir = os.path.join(get_ipython_dir(), 'log')
1017 if not os.path.isdir(log_dir):
1018 os.mkdir(log_dir, 0777)
1019 return log_dir
1020
1011 #****************************************************************************
1021 #****************************************************************************
1012 # strings and text
1022 # strings and text
1013
1023
1014 class LSString(str):
1024 class LSString(str):
1015 """String derivative with a special access attributes.
1025 """String derivative with a special access attributes.
1016
1026
1017 These are normal strings, but with the special attributes:
1027 These are normal strings, but with the special attributes:
1018
1028
1019 .l (or .list) : value as list (split on newlines).
1029 .l (or .list) : value as list (split on newlines).
1020 .n (or .nlstr): original value (the string itself).
1030 .n (or .nlstr): original value (the string itself).
1021 .s (or .spstr): value as whitespace-separated string.
1031 .s (or .spstr): value as whitespace-separated string.
1022 .p (or .paths): list of path objects
1032 .p (or .paths): list of path objects
1023
1033
1024 Any values which require transformations are computed only once and
1034 Any values which require transformations are computed only once and
1025 cached.
1035 cached.
1026
1036
1027 Such strings are very useful to efficiently interact with the shell, which
1037 Such strings are very useful to efficiently interact with the shell, which
1028 typically only understands whitespace-separated options for commands."""
1038 typically only understands whitespace-separated options for commands."""
1029
1039
1030 def get_list(self):
1040 def get_list(self):
1031 try:
1041 try:
1032 return self.__list
1042 return self.__list
1033 except AttributeError:
1043 except AttributeError:
1034 self.__list = self.split('\n')
1044 self.__list = self.split('\n')
1035 return self.__list
1045 return self.__list
1036
1046
1037 l = list = property(get_list)
1047 l = list = property(get_list)
1038
1048
1039 def get_spstr(self):
1049 def get_spstr(self):
1040 try:
1050 try:
1041 return self.__spstr
1051 return self.__spstr
1042 except AttributeError:
1052 except AttributeError:
1043 self.__spstr = self.replace('\n',' ')
1053 self.__spstr = self.replace('\n',' ')
1044 return self.__spstr
1054 return self.__spstr
1045
1055
1046 s = spstr = property(get_spstr)
1056 s = spstr = property(get_spstr)
1047
1057
1048 def get_nlstr(self):
1058 def get_nlstr(self):
1049 return self
1059 return self
1050
1060
1051 n = nlstr = property(get_nlstr)
1061 n = nlstr = property(get_nlstr)
1052
1062
1053 def get_paths(self):
1063 def get_paths(self):
1054 try:
1064 try:
1055 return self.__paths
1065 return self.__paths
1056 except AttributeError:
1066 except AttributeError:
1057 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1067 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1058 return self.__paths
1068 return self.__paths
1059
1069
1060 p = paths = property(get_paths)
1070 p = paths = property(get_paths)
1061
1071
1062 def print_lsstring(arg):
1072 def print_lsstring(arg):
1063 """ Prettier (non-repr-like) and more informative printer for LSString """
1073 """ Prettier (non-repr-like) and more informative printer for LSString """
1064 print "LSString (.p, .n, .l, .s available). Value:"
1074 print "LSString (.p, .n, .l, .s available). Value:"
1065 print arg
1075 print arg
1066
1076
1067 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1077 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1068
1078
1069 #----------------------------------------------------------------------------
1079 #----------------------------------------------------------------------------
1070 class SList(list):
1080 class SList(list):
1071 """List derivative with a special access attributes.
1081 """List derivative with a special access attributes.
1072
1082
1073 These are normal lists, but with the special attributes:
1083 These are normal lists, but with the special attributes:
1074
1084
1075 .l (or .list) : value as list (the list itself).
1085 .l (or .list) : value as list (the list itself).
1076 .n (or .nlstr): value as a string, joined on newlines.
1086 .n (or .nlstr): value as a string, joined on newlines.
1077 .s (or .spstr): value as a string, joined on spaces.
1087 .s (or .spstr): value as a string, joined on spaces.
1078 .p (or .paths): list of path objects
1088 .p (or .paths): list of path objects
1079
1089
1080 Any values which require transformations are computed only once and
1090 Any values which require transformations are computed only once and
1081 cached."""
1091 cached."""
1082
1092
1083 def get_list(self):
1093 def get_list(self):
1084 return self
1094 return self
1085
1095
1086 l = list = property(get_list)
1096 l = list = property(get_list)
1087
1097
1088 def get_spstr(self):
1098 def get_spstr(self):
1089 try:
1099 try:
1090 return self.__spstr
1100 return self.__spstr
1091 except AttributeError:
1101 except AttributeError:
1092 self.__spstr = ' '.join(self)
1102 self.__spstr = ' '.join(self)
1093 return self.__spstr
1103 return self.__spstr
1094
1104
1095 s = spstr = property(get_spstr)
1105 s = spstr = property(get_spstr)
1096
1106
1097 def get_nlstr(self):
1107 def get_nlstr(self):
1098 try:
1108 try:
1099 return self.__nlstr
1109 return self.__nlstr
1100 except AttributeError:
1110 except AttributeError:
1101 self.__nlstr = '\n'.join(self)
1111 self.__nlstr = '\n'.join(self)
1102 return self.__nlstr
1112 return self.__nlstr
1103
1113
1104 n = nlstr = property(get_nlstr)
1114 n = nlstr = property(get_nlstr)
1105
1115
1106 def get_paths(self):
1116 def get_paths(self):
1107 try:
1117 try:
1108 return self.__paths
1118 return self.__paths
1109 except AttributeError:
1119 except AttributeError:
1110 self.__paths = [path(p) for p in self if os.path.exists(p)]
1120 self.__paths = [path(p) for p in self if os.path.exists(p)]
1111 return self.__paths
1121 return self.__paths
1112
1122
1113 p = paths = property(get_paths)
1123 p = paths = property(get_paths)
1114
1124
1115 def grep(self, pattern, prune = False, field = None):
1125 def grep(self, pattern, prune = False, field = None):
1116 """ Return all strings matching 'pattern' (a regex or callable)
1126 """ Return all strings matching 'pattern' (a regex or callable)
1117
1127
1118 This is case-insensitive. If prune is true, return all items
1128 This is case-insensitive. If prune is true, return all items
1119 NOT matching the pattern.
1129 NOT matching the pattern.
1120
1130
1121 If field is specified, the match must occur in the specified
1131 If field is specified, the match must occur in the specified
1122 whitespace-separated field.
1132 whitespace-separated field.
1123
1133
1124 Examples::
1134 Examples::
1125
1135
1126 a.grep( lambda x: x.startswith('C') )
1136 a.grep( lambda x: x.startswith('C') )
1127 a.grep('Cha.*log', prune=1)
1137 a.grep('Cha.*log', prune=1)
1128 a.grep('chm', field=-1)
1138 a.grep('chm', field=-1)
1129 """
1139 """
1130
1140
1131 def match_target(s):
1141 def match_target(s):
1132 if field is None:
1142 if field is None:
1133 return s
1143 return s
1134 parts = s.split()
1144 parts = s.split()
1135 try:
1145 try:
1136 tgt = parts[field]
1146 tgt = parts[field]
1137 return tgt
1147 return tgt
1138 except IndexError:
1148 except IndexError:
1139 return ""
1149 return ""
1140
1150
1141 if isinstance(pattern, basestring):
1151 if isinstance(pattern, basestring):
1142 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1152 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1143 else:
1153 else:
1144 pred = pattern
1154 pred = pattern
1145 if not prune:
1155 if not prune:
1146 return SList([el for el in self if pred(match_target(el))])
1156 return SList([el for el in self if pred(match_target(el))])
1147 else:
1157 else:
1148 return SList([el for el in self if not pred(match_target(el))])
1158 return SList([el for el in self if not pred(match_target(el))])
1149 def fields(self, *fields):
1159 def fields(self, *fields):
1150 """ Collect whitespace-separated fields from string list
1160 """ Collect whitespace-separated fields from string list
1151
1161
1152 Allows quick awk-like usage of string lists.
1162 Allows quick awk-like usage of string lists.
1153
1163
1154 Example data (in var a, created by 'a = !ls -l')::
1164 Example data (in var a, created by 'a = !ls -l')::
1155 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1165 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1156 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1166 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1157
1167
1158 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1168 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1159 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1169 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1160 (note the joining by space).
1170 (note the joining by space).
1161 a.fields(-1) is ['ChangeLog', 'IPython']
1171 a.fields(-1) is ['ChangeLog', 'IPython']
1162
1172
1163 IndexErrors are ignored.
1173 IndexErrors are ignored.
1164
1174
1165 Without args, fields() just split()'s the strings.
1175 Without args, fields() just split()'s the strings.
1166 """
1176 """
1167 if len(fields) == 0:
1177 if len(fields) == 0:
1168 return [el.split() for el in self]
1178 return [el.split() for el in self]
1169
1179
1170 res = SList()
1180 res = SList()
1171 for el in [f.split() for f in self]:
1181 for el in [f.split() for f in self]:
1172 lineparts = []
1182 lineparts = []
1173
1183
1174 for fd in fields:
1184 for fd in fields:
1175 try:
1185 try:
1176 lineparts.append(el[fd])
1186 lineparts.append(el[fd])
1177 except IndexError:
1187 except IndexError:
1178 pass
1188 pass
1179 if lineparts:
1189 if lineparts:
1180 res.append(" ".join(lineparts))
1190 res.append(" ".join(lineparts))
1181
1191
1182 return res
1192 return res
1183 def sort(self,field= None, nums = False):
1193 def sort(self,field= None, nums = False):
1184 """ sort by specified fields (see fields())
1194 """ sort by specified fields (see fields())
1185
1195
1186 Example::
1196 Example::
1187 a.sort(1, nums = True)
1197 a.sort(1, nums = True)
1188
1198
1189 Sorts a by second field, in numerical order (so that 21 > 3)
1199 Sorts a by second field, in numerical order (so that 21 > 3)
1190
1200
1191 """
1201 """
1192
1202
1193 #decorate, sort, undecorate
1203 #decorate, sort, undecorate
1194 if field is not None:
1204 if field is not None:
1195 dsu = [[SList([line]).fields(field), line] for line in self]
1205 dsu = [[SList([line]).fields(field), line] for line in self]
1196 else:
1206 else:
1197 dsu = [[line, line] for line in self]
1207 dsu = [[line, line] for line in self]
1198 if nums:
1208 if nums:
1199 for i in range(len(dsu)):
1209 for i in range(len(dsu)):
1200 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1210 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1201 try:
1211 try:
1202 n = int(numstr)
1212 n = int(numstr)
1203 except ValueError:
1213 except ValueError:
1204 n = 0;
1214 n = 0;
1205 dsu[i][0] = n
1215 dsu[i][0] = n
1206
1216
1207
1217
1208 dsu.sort()
1218 dsu.sort()
1209 return SList([t[1] for t in dsu])
1219 return SList([t[1] for t in dsu])
1210
1220
1211 def print_slist(arg):
1221 def print_slist(arg):
1212 """ Prettier (non-repr-like) and more informative printer for SList """
1222 """ Prettier (non-repr-like) and more informative printer for SList """
1213 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1223 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1214 if hasattr(arg, 'hideonce') and arg.hideonce:
1224 if hasattr(arg, 'hideonce') and arg.hideonce:
1215 arg.hideonce = False
1225 arg.hideonce = False
1216 return
1226 return
1217
1227
1218 nlprint(arg)
1228 nlprint(arg)
1219
1229
1220 print_slist = result_display.when_type(SList)(print_slist)
1230 print_slist = result_display.when_type(SList)(print_slist)
1221
1231
1222
1232
1223
1233
1224 #----------------------------------------------------------------------------
1234 #----------------------------------------------------------------------------
1225 def esc_quotes(strng):
1235 def esc_quotes(strng):
1226 """Return the input string with single and double quotes escaped out"""
1236 """Return the input string with single and double quotes escaped out"""
1227
1237
1228 return strng.replace('"','\\"').replace("'","\\'")
1238 return strng.replace('"','\\"').replace("'","\\'")
1229
1239
1230 #----------------------------------------------------------------------------
1240 #----------------------------------------------------------------------------
1231 def make_quoted_expr(s):
1241 def make_quoted_expr(s):
1232 """Return string s in appropriate quotes, using raw string if possible.
1242 """Return string s in appropriate quotes, using raw string if possible.
1233
1243
1234 XXX - example removed because it caused encoding errors in documentation
1244 XXX - example removed because it caused encoding errors in documentation
1235 generation. We need a new example that doesn't contain invalid chars.
1245 generation. We need a new example that doesn't contain invalid chars.
1236
1246
1237 Note the use of raw string and padding at the end to allow trailing
1247 Note the use of raw string and padding at the end to allow trailing
1238 backslash.
1248 backslash.
1239 """
1249 """
1240
1250
1241 tail = ''
1251 tail = ''
1242 tailpadding = ''
1252 tailpadding = ''
1243 raw = ''
1253 raw = ''
1244 if "\\" in s:
1254 if "\\" in s:
1245 raw = 'r'
1255 raw = 'r'
1246 if s.endswith('\\'):
1256 if s.endswith('\\'):
1247 tail = '[:-1]'
1257 tail = '[:-1]'
1248 tailpadding = '_'
1258 tailpadding = '_'
1249 if '"' not in s:
1259 if '"' not in s:
1250 quote = '"'
1260 quote = '"'
1251 elif "'" not in s:
1261 elif "'" not in s:
1252 quote = "'"
1262 quote = "'"
1253 elif '"""' not in s and not s.endswith('"'):
1263 elif '"""' not in s and not s.endswith('"'):
1254 quote = '"""'
1264 quote = '"""'
1255 elif "'''" not in s and not s.endswith("'"):
1265 elif "'''" not in s and not s.endswith("'"):
1256 quote = "'''"
1266 quote = "'''"
1257 else:
1267 else:
1258 # give up, backslash-escaped string will do
1268 # give up, backslash-escaped string will do
1259 return '"%s"' % esc_quotes(s)
1269 return '"%s"' % esc_quotes(s)
1260 res = raw + quote + s + tailpadding + quote + tail
1270 res = raw + quote + s + tailpadding + quote + tail
1261 return res
1271 return res
1262
1272
1263
1273
1264 #----------------------------------------------------------------------------
1274 #----------------------------------------------------------------------------
1265 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1275 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1266 """Take multiple lines of input.
1276 """Take multiple lines of input.
1267
1277
1268 A list with each line of input as a separate element is returned when a
1278 A list with each line of input as a separate element is returned when a
1269 termination string is entered (defaults to a single '.'). Input can also
1279 termination string is entered (defaults to a single '.'). Input can also
1270 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1280 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1271
1281
1272 Lines of input which end in \\ are joined into single entries (and a
1282 Lines of input which end in \\ are joined into single entries (and a
1273 secondary continuation prompt is issued as long as the user terminates
1283 secondary continuation prompt is issued as long as the user terminates
1274 lines with \\). This allows entering very long strings which are still
1284 lines with \\). This allows entering very long strings which are still
1275 meant to be treated as single entities.
1285 meant to be treated as single entities.
1276 """
1286 """
1277
1287
1278 try:
1288 try:
1279 if header:
1289 if header:
1280 header += '\n'
1290 header += '\n'
1281 lines = [raw_input(header + ps1)]
1291 lines = [raw_input(header + ps1)]
1282 except EOFError:
1292 except EOFError:
1283 return []
1293 return []
1284 terminate = [terminate_str]
1294 terminate = [terminate_str]
1285 try:
1295 try:
1286 while lines[-1:] != terminate:
1296 while lines[-1:] != terminate:
1287 new_line = raw_input(ps1)
1297 new_line = raw_input(ps1)
1288 while new_line.endswith('\\'):
1298 while new_line.endswith('\\'):
1289 new_line = new_line[:-1] + raw_input(ps2)
1299 new_line = new_line[:-1] + raw_input(ps2)
1290 lines.append(new_line)
1300 lines.append(new_line)
1291
1301
1292 return lines[:-1] # don't return the termination command
1302 return lines[:-1] # don't return the termination command
1293 except EOFError:
1303 except EOFError:
1294 print
1304 print
1295 return lines
1305 return lines
1296
1306
1297 #----------------------------------------------------------------------------
1307 #----------------------------------------------------------------------------
1298 def raw_input_ext(prompt='', ps2='... '):
1308 def raw_input_ext(prompt='', ps2='... '):
1299 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1309 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1300
1310
1301 line = raw_input(prompt)
1311 line = raw_input(prompt)
1302 while line.endswith('\\'):
1312 while line.endswith('\\'):
1303 line = line[:-1] + raw_input(ps2)
1313 line = line[:-1] + raw_input(ps2)
1304 return line
1314 return line
1305
1315
1306 #----------------------------------------------------------------------------
1316 #----------------------------------------------------------------------------
1307 def ask_yes_no(prompt,default=None):
1317 def ask_yes_no(prompt,default=None):
1308 """Asks a question and returns a boolean (y/n) answer.
1318 """Asks a question and returns a boolean (y/n) answer.
1309
1319
1310 If default is given (one of 'y','n'), it is used if the user input is
1320 If default is given (one of 'y','n'), it is used if the user input is
1311 empty. Otherwise the question is repeated until an answer is given.
1321 empty. Otherwise the question is repeated until an answer is given.
1312
1322
1313 An EOF is treated as the default answer. If there is no default, an
1323 An EOF is treated as the default answer. If there is no default, an
1314 exception is raised to prevent infinite loops.
1324 exception is raised to prevent infinite loops.
1315
1325
1316 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1326 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1317
1327
1318 answers = {'y':True,'n':False,'yes':True,'no':False}
1328 answers = {'y':True,'n':False,'yes':True,'no':False}
1319 ans = None
1329 ans = None
1320 while ans not in answers.keys():
1330 while ans not in answers.keys():
1321 try:
1331 try:
1322 ans = raw_input(prompt+' ').lower()
1332 ans = raw_input(prompt+' ').lower()
1323 if not ans: # response was an empty string
1333 if not ans: # response was an empty string
1324 ans = default
1334 ans = default
1325 except KeyboardInterrupt:
1335 except KeyboardInterrupt:
1326 pass
1336 pass
1327 except EOFError:
1337 except EOFError:
1328 if default in answers.keys():
1338 if default in answers.keys():
1329 ans = default
1339 ans = default
1330 print
1340 print
1331 else:
1341 else:
1332 raise
1342 raise
1333
1343
1334 return answers[ans]
1344 return answers[ans]
1335
1345
1336 #----------------------------------------------------------------------------
1346 #----------------------------------------------------------------------------
1337 def marquee(txt='',width=78,mark='*'):
1347 def marquee(txt='',width=78,mark='*'):
1338 """Return the input string centered in a 'marquee'."""
1348 """Return the input string centered in a 'marquee'."""
1339 if not txt:
1349 if not txt:
1340 return (mark*width)[:width]
1350 return (mark*width)[:width]
1341 nmark = (width-len(txt)-2)/len(mark)/2
1351 nmark = (width-len(txt)-2)/len(mark)/2
1342 if nmark < 0: nmark =0
1352 if nmark < 0: nmark =0
1343 marks = mark*nmark
1353 marks = mark*nmark
1344 return '%s %s %s' % (marks,txt,marks)
1354 return '%s %s %s' % (marks,txt,marks)
1345
1355
1346 #----------------------------------------------------------------------------
1356 #----------------------------------------------------------------------------
1347 class EvalDict:
1357 class EvalDict:
1348 """
1358 """
1349 Emulate a dict which evaluates its contents in the caller's frame.
1359 Emulate a dict which evaluates its contents in the caller's frame.
1350
1360
1351 Usage:
1361 Usage:
1352 >>> number = 19
1362 >>> number = 19
1353
1363
1354 >>> text = "python"
1364 >>> text = "python"
1355
1365
1356 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1366 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1357 Python 2.1 rules!
1367 Python 2.1 rules!
1358 """
1368 """
1359
1369
1360 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1370 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1361 # modified (shorter) version of:
1371 # modified (shorter) version of:
1362 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1372 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1363 # Skip Montanaro (skip@pobox.com).
1373 # Skip Montanaro (skip@pobox.com).
1364
1374
1365 def __getitem__(self, name):
1375 def __getitem__(self, name):
1366 frame = sys._getframe(1)
1376 frame = sys._getframe(1)
1367 return eval(name, frame.f_globals, frame.f_locals)
1377 return eval(name, frame.f_globals, frame.f_locals)
1368
1378
1369 EvalString = EvalDict # for backwards compatibility
1379 EvalString = EvalDict # for backwards compatibility
1370 #----------------------------------------------------------------------------
1380 #----------------------------------------------------------------------------
1371 def qw(words,flat=0,sep=None,maxsplit=-1):
1381 def qw(words,flat=0,sep=None,maxsplit=-1):
1372 """Similar to Perl's qw() operator, but with some more options.
1382 """Similar to Perl's qw() operator, but with some more options.
1373
1383
1374 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1384 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1375
1385
1376 words can also be a list itself, and with flat=1, the output will be
1386 words can also be a list itself, and with flat=1, the output will be
1377 recursively flattened.
1387 recursively flattened.
1378
1388
1379 Examples:
1389 Examples:
1380
1390
1381 >>> qw('1 2')
1391 >>> qw('1 2')
1382 ['1', '2']
1392 ['1', '2']
1383
1393
1384 >>> qw(['a b','1 2',['m n','p q']])
1394 >>> qw(['a b','1 2',['m n','p q']])
1385 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1395 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1386
1396
1387 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1397 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1388 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1398 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1389 """
1399 """
1390
1400
1391 if type(words) in StringTypes:
1401 if type(words) in StringTypes:
1392 return [word.strip() for word in words.split(sep,maxsplit)
1402 return [word.strip() for word in words.split(sep,maxsplit)
1393 if word and not word.isspace() ]
1403 if word and not word.isspace() ]
1394 if flat:
1404 if flat:
1395 return flatten(map(qw,words,[1]*len(words)))
1405 return flatten(map(qw,words,[1]*len(words)))
1396 return map(qw,words)
1406 return map(qw,words)
1397
1407
1398 #----------------------------------------------------------------------------
1408 #----------------------------------------------------------------------------
1399 def qwflat(words,sep=None,maxsplit=-1):
1409 def qwflat(words,sep=None,maxsplit=-1):
1400 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1410 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1401 return qw(words,1,sep,maxsplit)
1411 return qw(words,1,sep,maxsplit)
1402
1412
1403 #----------------------------------------------------------------------------
1413 #----------------------------------------------------------------------------
1404 def qw_lol(indata):
1414 def qw_lol(indata):
1405 """qw_lol('a b') -> [['a','b']],
1415 """qw_lol('a b') -> [['a','b']],
1406 otherwise it's just a call to qw().
1416 otherwise it's just a call to qw().
1407
1417
1408 We need this to make sure the modules_some keys *always* end up as a
1418 We need this to make sure the modules_some keys *always* end up as a
1409 list of lists."""
1419 list of lists."""
1410
1420
1411 if type(indata) in StringTypes:
1421 if type(indata) in StringTypes:
1412 return [qw(indata)]
1422 return [qw(indata)]
1413 else:
1423 else:
1414 return qw(indata)
1424 return qw(indata)
1415
1425
1416 #-----------------------------------------------------------------------------
1426 #-----------------------------------------------------------------------------
1417 def list_strings(arg):
1427 def list_strings(arg):
1418 """Always return a list of strings, given a string or list of strings
1428 """Always return a list of strings, given a string or list of strings
1419 as input."""
1429 as input."""
1420
1430
1421 if type(arg) in StringTypes: return [arg]
1431 if type(arg) in StringTypes: return [arg]
1422 else: return arg
1432 else: return arg
1423
1433
1424 #----------------------------------------------------------------------------
1434 #----------------------------------------------------------------------------
1425 def grep(pat,list,case=1):
1435 def grep(pat,list,case=1):
1426 """Simple minded grep-like function.
1436 """Simple minded grep-like function.
1427 grep(pat,list) returns occurrences of pat in list, None on failure.
1437 grep(pat,list) returns occurrences of pat in list, None on failure.
1428
1438
1429 It only does simple string matching, with no support for regexps. Use the
1439 It only does simple string matching, with no support for regexps. Use the
1430 option case=0 for case-insensitive matching."""
1440 option case=0 for case-insensitive matching."""
1431
1441
1432 # This is pretty crude. At least it should implement copying only references
1442 # This is pretty crude. At least it should implement copying only references
1433 # to the original data in case it's big. Now it copies the data for output.
1443 # to the original data in case it's big. Now it copies the data for output.
1434 out=[]
1444 out=[]
1435 if case:
1445 if case:
1436 for term in list:
1446 for term in list:
1437 if term.find(pat)>-1: out.append(term)
1447 if term.find(pat)>-1: out.append(term)
1438 else:
1448 else:
1439 lpat=pat.lower()
1449 lpat=pat.lower()
1440 for term in list:
1450 for term in list:
1441 if term.lower().find(lpat)>-1: out.append(term)
1451 if term.lower().find(lpat)>-1: out.append(term)
1442
1452
1443 if len(out): return out
1453 if len(out): return out
1444 else: return None
1454 else: return None
1445
1455
1446 #----------------------------------------------------------------------------
1456 #----------------------------------------------------------------------------
1447 def dgrep(pat,*opts):
1457 def dgrep(pat,*opts):
1448 """Return grep() on dir()+dir(__builtins__).
1458 """Return grep() on dir()+dir(__builtins__).
1449
1459
1450 A very common use of grep() when working interactively."""
1460 A very common use of grep() when working interactively."""
1451
1461
1452 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1462 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1453
1463
1454 #----------------------------------------------------------------------------
1464 #----------------------------------------------------------------------------
1455 def idgrep(pat):
1465 def idgrep(pat):
1456 """Case-insensitive dgrep()"""
1466 """Case-insensitive dgrep()"""
1457
1467
1458 return dgrep(pat,0)
1468 return dgrep(pat,0)
1459
1469
1460 #----------------------------------------------------------------------------
1470 #----------------------------------------------------------------------------
1461 def igrep(pat,list):
1471 def igrep(pat,list):
1462 """Synonym for case-insensitive grep."""
1472 """Synonym for case-insensitive grep."""
1463
1473
1464 return grep(pat,list,case=0)
1474 return grep(pat,list,case=0)
1465
1475
1466 #----------------------------------------------------------------------------
1476 #----------------------------------------------------------------------------
1467 def indent(str,nspaces=4,ntabs=0):
1477 def indent(str,nspaces=4,ntabs=0):
1468 """Indent a string a given number of spaces or tabstops.
1478 """Indent a string a given number of spaces or tabstops.
1469
1479
1470 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1480 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1471 """
1481 """
1472 if str is None:
1482 if str is None:
1473 return
1483 return
1474 ind = '\t'*ntabs+' '*nspaces
1484 ind = '\t'*ntabs+' '*nspaces
1475 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1485 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1476 if outstr.endswith(os.linesep+ind):
1486 if outstr.endswith(os.linesep+ind):
1477 return outstr[:-len(ind)]
1487 return outstr[:-len(ind)]
1478 else:
1488 else:
1479 return outstr
1489 return outstr
1480
1490
1481 #-----------------------------------------------------------------------------
1491 #-----------------------------------------------------------------------------
1482 def native_line_ends(filename,backup=1):
1492 def native_line_ends(filename,backup=1):
1483 """Convert (in-place) a file to line-ends native to the current OS.
1493 """Convert (in-place) a file to line-ends native to the current OS.
1484
1494
1485 If the optional backup argument is given as false, no backup of the
1495 If the optional backup argument is given as false, no backup of the
1486 original file is left. """
1496 original file is left. """
1487
1497
1488 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1498 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1489
1499
1490 bak_filename = filename + backup_suffixes[os.name]
1500 bak_filename = filename + backup_suffixes[os.name]
1491
1501
1492 original = open(filename).read()
1502 original = open(filename).read()
1493 shutil.copy2(filename,bak_filename)
1503 shutil.copy2(filename,bak_filename)
1494 try:
1504 try:
1495 new = open(filename,'wb')
1505 new = open(filename,'wb')
1496 new.write(os.linesep.join(original.splitlines()))
1506 new.write(os.linesep.join(original.splitlines()))
1497 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1507 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1498 new.close()
1508 new.close()
1499 except:
1509 except:
1500 os.rename(bak_filename,filename)
1510 os.rename(bak_filename,filename)
1501 if not backup:
1511 if not backup:
1502 try:
1512 try:
1503 os.remove(bak_filename)
1513 os.remove(bak_filename)
1504 except:
1514 except:
1505 pass
1515 pass
1506
1516
1507 #----------------------------------------------------------------------------
1517 #----------------------------------------------------------------------------
1508 def get_pager_cmd(pager_cmd = None):
1518 def get_pager_cmd(pager_cmd = None):
1509 """Return a pager command.
1519 """Return a pager command.
1510
1520
1511 Makes some attempts at finding an OS-correct one."""
1521 Makes some attempts at finding an OS-correct one."""
1512
1522
1513 if os.name == 'posix':
1523 if os.name == 'posix':
1514 default_pager_cmd = 'less -r' # -r for color control sequences
1524 default_pager_cmd = 'less -r' # -r for color control sequences
1515 elif os.name in ['nt','dos']:
1525 elif os.name in ['nt','dos']:
1516 default_pager_cmd = 'type'
1526 default_pager_cmd = 'type'
1517
1527
1518 if pager_cmd is None:
1528 if pager_cmd is None:
1519 try:
1529 try:
1520 pager_cmd = os.environ['PAGER']
1530 pager_cmd = os.environ['PAGER']
1521 except:
1531 except:
1522 pager_cmd = default_pager_cmd
1532 pager_cmd = default_pager_cmd
1523 return pager_cmd
1533 return pager_cmd
1524
1534
1525 #-----------------------------------------------------------------------------
1535 #-----------------------------------------------------------------------------
1526 def get_pager_start(pager,start):
1536 def get_pager_start(pager,start):
1527 """Return the string for paging files with an offset.
1537 """Return the string for paging files with an offset.
1528
1538
1529 This is the '+N' argument which less and more (under Unix) accept.
1539 This is the '+N' argument which less and more (under Unix) accept.
1530 """
1540 """
1531
1541
1532 if pager in ['less','more']:
1542 if pager in ['less','more']:
1533 if start:
1543 if start:
1534 start_string = '+' + str(start)
1544 start_string = '+' + str(start)
1535 else:
1545 else:
1536 start_string = ''
1546 start_string = ''
1537 else:
1547 else:
1538 start_string = ''
1548 start_string = ''
1539 return start_string
1549 return start_string
1540
1550
1541 #----------------------------------------------------------------------------
1551 #----------------------------------------------------------------------------
1542 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1552 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1543 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1553 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1544 import msvcrt
1554 import msvcrt
1545 def page_more():
1555 def page_more():
1546 """ Smart pausing between pages
1556 """ Smart pausing between pages
1547
1557
1548 @return: True if need print more lines, False if quit
1558 @return: True if need print more lines, False if quit
1549 """
1559 """
1550 Term.cout.write('---Return to continue, q to quit--- ')
1560 Term.cout.write('---Return to continue, q to quit--- ')
1551 ans = msvcrt.getch()
1561 ans = msvcrt.getch()
1552 if ans in ("q", "Q"):
1562 if ans in ("q", "Q"):
1553 result = False
1563 result = False
1554 else:
1564 else:
1555 result = True
1565 result = True
1556 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1566 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1557 return result
1567 return result
1558 else:
1568 else:
1559 def page_more():
1569 def page_more():
1560 ans = raw_input('---Return to continue, q to quit--- ')
1570 ans = raw_input('---Return to continue, q to quit--- ')
1561 if ans.lower().startswith('q'):
1571 if ans.lower().startswith('q'):
1562 return False
1572 return False
1563 else:
1573 else:
1564 return True
1574 return True
1565
1575
1566 esc_re = re.compile(r"(\x1b[^m]+m)")
1576 esc_re = re.compile(r"(\x1b[^m]+m)")
1567
1577
1568 def page_dumb(strng,start=0,screen_lines=25):
1578 def page_dumb(strng,start=0,screen_lines=25):
1569 """Very dumb 'pager' in Python, for when nothing else works.
1579 """Very dumb 'pager' in Python, for when nothing else works.
1570
1580
1571 Only moves forward, same interface as page(), except for pager_cmd and
1581 Only moves forward, same interface as page(), except for pager_cmd and
1572 mode."""
1582 mode."""
1573
1583
1574 out_ln = strng.splitlines()[start:]
1584 out_ln = strng.splitlines()[start:]
1575 screens = chop(out_ln,screen_lines-1)
1585 screens = chop(out_ln,screen_lines-1)
1576 if len(screens) == 1:
1586 if len(screens) == 1:
1577 print >>Term.cout, os.linesep.join(screens[0])
1587 print >>Term.cout, os.linesep.join(screens[0])
1578 else:
1588 else:
1579 last_escape = ""
1589 last_escape = ""
1580 for scr in screens[0:-1]:
1590 for scr in screens[0:-1]:
1581 hunk = os.linesep.join(scr)
1591 hunk = os.linesep.join(scr)
1582 print >>Term.cout, last_escape + hunk
1592 print >>Term.cout, last_escape + hunk
1583 if not page_more():
1593 if not page_more():
1584 return
1594 return
1585 esc_list = esc_re.findall(hunk)
1595 esc_list = esc_re.findall(hunk)
1586 if len(esc_list) > 0:
1596 if len(esc_list) > 0:
1587 last_escape = esc_list[-1]
1597 last_escape = esc_list[-1]
1588 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1598 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1589
1599
1590 #----------------------------------------------------------------------------
1600 #----------------------------------------------------------------------------
1591 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1601 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1592 """Print a string, piping through a pager after a certain length.
1602 """Print a string, piping through a pager after a certain length.
1593
1603
1594 The screen_lines parameter specifies the number of *usable* lines of your
1604 The screen_lines parameter specifies the number of *usable* lines of your
1595 terminal screen (total lines minus lines you need to reserve to show other
1605 terminal screen (total lines minus lines you need to reserve to show other
1596 information).
1606 information).
1597
1607
1598 If you set screen_lines to a number <=0, page() will try to auto-determine
1608 If you set screen_lines to a number <=0, page() will try to auto-determine
1599 your screen size and will only use up to (screen_size+screen_lines) for
1609 your screen size and will only use up to (screen_size+screen_lines) for
1600 printing, paging after that. That is, if you want auto-detection but need
1610 printing, paging after that. That is, if you want auto-detection but need
1601 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1611 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1602 auto-detection without any lines reserved simply use screen_lines = 0.
1612 auto-detection without any lines reserved simply use screen_lines = 0.
1603
1613
1604 If a string won't fit in the allowed lines, it is sent through the
1614 If a string won't fit in the allowed lines, it is sent through the
1605 specified pager command. If none given, look for PAGER in the environment,
1615 specified pager command. If none given, look for PAGER in the environment,
1606 and ultimately default to less.
1616 and ultimately default to less.
1607
1617
1608 If no system pager works, the string is sent through a 'dumb pager'
1618 If no system pager works, the string is sent through a 'dumb pager'
1609 written in python, very simplistic.
1619 written in python, very simplistic.
1610 """
1620 """
1611
1621
1612 # Some routines may auto-compute start offsets incorrectly and pass a
1622 # Some routines may auto-compute start offsets incorrectly and pass a
1613 # negative value. Offset to 0 for robustness.
1623 # negative value. Offset to 0 for robustness.
1614 start = max(0,start)
1624 start = max(0,start)
1615
1625
1616 # first, try the hook
1626 # first, try the hook
1617 ip = IPython.ipapi.get()
1627 ip = IPython.ipapi.get()
1618 if ip:
1628 if ip:
1619 try:
1629 try:
1620 ip.IP.hooks.show_in_pager(strng)
1630 ip.IP.hooks.show_in_pager(strng)
1621 return
1631 return
1622 except IPython.ipapi.TryNext:
1632 except IPython.ipapi.TryNext:
1623 pass
1633 pass
1624
1634
1625 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1635 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1626 TERM = os.environ.get('TERM','dumb')
1636 TERM = os.environ.get('TERM','dumb')
1627 if TERM in ['dumb','emacs'] and os.name != 'nt':
1637 if TERM in ['dumb','emacs'] and os.name != 'nt':
1628 print strng
1638 print strng
1629 return
1639 return
1630 # chop off the topmost part of the string we don't want to see
1640 # chop off the topmost part of the string we don't want to see
1631 str_lines = strng.split(os.linesep)[start:]
1641 str_lines = strng.split(os.linesep)[start:]
1632 str_toprint = os.linesep.join(str_lines)
1642 str_toprint = os.linesep.join(str_lines)
1633 num_newlines = len(str_lines)
1643 num_newlines = len(str_lines)
1634 len_str = len(str_toprint)
1644 len_str = len(str_toprint)
1635
1645
1636 # Dumb heuristics to guesstimate number of on-screen lines the string
1646 # Dumb heuristics to guesstimate number of on-screen lines the string
1637 # takes. Very basic, but good enough for docstrings in reasonable
1647 # takes. Very basic, but good enough for docstrings in reasonable
1638 # terminals. If someone later feels like refining it, it's not hard.
1648 # terminals. If someone later feels like refining it, it's not hard.
1639 numlines = max(num_newlines,int(len_str/80)+1)
1649 numlines = max(num_newlines,int(len_str/80)+1)
1640
1650
1641 if os.name == "nt":
1651 if os.name == "nt":
1642 screen_lines_def = get_console_size(defaulty=25)[1]
1652 screen_lines_def = get_console_size(defaulty=25)[1]
1643 else:
1653 else:
1644 screen_lines_def = 25 # default value if we can't auto-determine
1654 screen_lines_def = 25 # default value if we can't auto-determine
1645
1655
1646 # auto-determine screen size
1656 # auto-determine screen size
1647 if screen_lines <= 0:
1657 if screen_lines <= 0:
1648 if TERM=='xterm':
1658 if TERM=='xterm':
1649 use_curses = USE_CURSES
1659 use_curses = USE_CURSES
1650 else:
1660 else:
1651 # curses causes problems on many terminals other than xterm.
1661 # curses causes problems on many terminals other than xterm.
1652 use_curses = False
1662 use_curses = False
1653 if use_curses:
1663 if use_curses:
1654 # There is a bug in curses, where *sometimes* it fails to properly
1664 # There is a bug in curses, where *sometimes* it fails to properly
1655 # initialize, and then after the endwin() call is made, the
1665 # initialize, and then after the endwin() call is made, the
1656 # terminal is left in an unusable state. Rather than trying to
1666 # terminal is left in an unusable state. Rather than trying to
1657 # check everytime for this (by requesting and comparing termios
1667 # check everytime for this (by requesting and comparing termios
1658 # flags each time), we just save the initial terminal state and
1668 # flags each time), we just save the initial terminal state and
1659 # unconditionally reset it every time. It's cheaper than making
1669 # unconditionally reset it every time. It's cheaper than making
1660 # the checks.
1670 # the checks.
1661 term_flags = termios.tcgetattr(sys.stdout)
1671 term_flags = termios.tcgetattr(sys.stdout)
1662 scr = curses.initscr()
1672 scr = curses.initscr()
1663 screen_lines_real,screen_cols = scr.getmaxyx()
1673 screen_lines_real,screen_cols = scr.getmaxyx()
1664 curses.endwin()
1674 curses.endwin()
1665 # Restore terminal state in case endwin() didn't.
1675 # Restore terminal state in case endwin() didn't.
1666 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1676 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1667 # Now we have what we needed: the screen size in rows/columns
1677 # Now we have what we needed: the screen size in rows/columns
1668 screen_lines += screen_lines_real
1678 screen_lines += screen_lines_real
1669 #print '***Screen size:',screen_lines_real,'lines x',\
1679 #print '***Screen size:',screen_lines_real,'lines x',\
1670 #screen_cols,'columns.' # dbg
1680 #screen_cols,'columns.' # dbg
1671 else:
1681 else:
1672 screen_lines += screen_lines_def
1682 screen_lines += screen_lines_def
1673
1683
1674 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1684 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1675 if numlines <= screen_lines :
1685 if numlines <= screen_lines :
1676 #print '*** normal print' # dbg
1686 #print '*** normal print' # dbg
1677 print >>Term.cout, str_toprint
1687 print >>Term.cout, str_toprint
1678 else:
1688 else:
1679 # Try to open pager and default to internal one if that fails.
1689 # Try to open pager and default to internal one if that fails.
1680 # All failure modes are tagged as 'retval=1', to match the return
1690 # All failure modes are tagged as 'retval=1', to match the return
1681 # value of a failed system command. If any intermediate attempt
1691 # value of a failed system command. If any intermediate attempt
1682 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1692 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1683 pager_cmd = get_pager_cmd(pager_cmd)
1693 pager_cmd = get_pager_cmd(pager_cmd)
1684 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1694 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1685 if os.name == 'nt':
1695 if os.name == 'nt':
1686 if pager_cmd.startswith('type'):
1696 if pager_cmd.startswith('type'):
1687 # The default WinXP 'type' command is failing on complex strings.
1697 # The default WinXP 'type' command is failing on complex strings.
1688 retval = 1
1698 retval = 1
1689 else:
1699 else:
1690 tmpname = tempfile.mktemp('.txt')
1700 tmpname = tempfile.mktemp('.txt')
1691 tmpfile = file(tmpname,'wt')
1701 tmpfile = file(tmpname,'wt')
1692 tmpfile.write(strng)
1702 tmpfile.write(strng)
1693 tmpfile.close()
1703 tmpfile.close()
1694 cmd = "%s < %s" % (pager_cmd,tmpname)
1704 cmd = "%s < %s" % (pager_cmd,tmpname)
1695 if os.system(cmd):
1705 if os.system(cmd):
1696 retval = 1
1706 retval = 1
1697 else:
1707 else:
1698 retval = None
1708 retval = None
1699 os.remove(tmpname)
1709 os.remove(tmpname)
1700 else:
1710 else:
1701 try:
1711 try:
1702 retval = None
1712 retval = None
1703 # if I use popen4, things hang. No idea why.
1713 # if I use popen4, things hang. No idea why.
1704 #pager,shell_out = os.popen4(pager_cmd)
1714 #pager,shell_out = os.popen4(pager_cmd)
1705 pager = os.popen(pager_cmd,'w')
1715 pager = os.popen(pager_cmd,'w')
1706 pager.write(strng)
1716 pager.write(strng)
1707 pager.close()
1717 pager.close()
1708 retval = pager.close() # success returns None
1718 retval = pager.close() # success returns None
1709 except IOError,msg: # broken pipe when user quits
1719 except IOError,msg: # broken pipe when user quits
1710 if msg.args == (32,'Broken pipe'):
1720 if msg.args == (32,'Broken pipe'):
1711 retval = None
1721 retval = None
1712 else:
1722 else:
1713 retval = 1
1723 retval = 1
1714 except OSError:
1724 except OSError:
1715 # Other strange problems, sometimes seen in Win2k/cygwin
1725 # Other strange problems, sometimes seen in Win2k/cygwin
1716 retval = 1
1726 retval = 1
1717 if retval is not None:
1727 if retval is not None:
1718 page_dumb(strng,screen_lines=screen_lines)
1728 page_dumb(strng,screen_lines=screen_lines)
1719
1729
1720 #----------------------------------------------------------------------------
1730 #----------------------------------------------------------------------------
1721 def page_file(fname,start = 0, pager_cmd = None):
1731 def page_file(fname,start = 0, pager_cmd = None):
1722 """Page a file, using an optional pager command and starting line.
1732 """Page a file, using an optional pager command and starting line.
1723 """
1733 """
1724
1734
1725 pager_cmd = get_pager_cmd(pager_cmd)
1735 pager_cmd = get_pager_cmd(pager_cmd)
1726 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1736 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1727
1737
1728 try:
1738 try:
1729 if os.environ['TERM'] in ['emacs','dumb']:
1739 if os.environ['TERM'] in ['emacs','dumb']:
1730 raise EnvironmentError
1740 raise EnvironmentError
1731 xsys(pager_cmd + ' ' + fname)
1741 xsys(pager_cmd + ' ' + fname)
1732 except:
1742 except:
1733 try:
1743 try:
1734 if start > 0:
1744 if start > 0:
1735 start -= 1
1745 start -= 1
1736 page(open(fname).read(),start)
1746 page(open(fname).read(),start)
1737 except:
1747 except:
1738 print 'Unable to show file',`fname`
1748 print 'Unable to show file',`fname`
1739
1749
1740
1750
1741 #----------------------------------------------------------------------------
1751 #----------------------------------------------------------------------------
1742 def snip_print(str,width = 75,print_full = 0,header = ''):
1752 def snip_print(str,width = 75,print_full = 0,header = ''):
1743 """Print a string snipping the midsection to fit in width.
1753 """Print a string snipping the midsection to fit in width.
1744
1754
1745 print_full: mode control:
1755 print_full: mode control:
1746 - 0: only snip long strings
1756 - 0: only snip long strings
1747 - 1: send to page() directly.
1757 - 1: send to page() directly.
1748 - 2: snip long strings and ask for full length viewing with page()
1758 - 2: snip long strings and ask for full length viewing with page()
1749 Return 1 if snipping was necessary, 0 otherwise."""
1759 Return 1 if snipping was necessary, 0 otherwise."""
1750
1760
1751 if print_full == 1:
1761 if print_full == 1:
1752 page(header+str)
1762 page(header+str)
1753 return 0
1763 return 0
1754
1764
1755 print header,
1765 print header,
1756 if len(str) < width:
1766 if len(str) < width:
1757 print str
1767 print str
1758 snip = 0
1768 snip = 0
1759 else:
1769 else:
1760 whalf = int((width -5)/2)
1770 whalf = int((width -5)/2)
1761 print str[:whalf] + ' <...> ' + str[-whalf:]
1771 print str[:whalf] + ' <...> ' + str[-whalf:]
1762 snip = 1
1772 snip = 1
1763 if snip and print_full == 2:
1773 if snip and print_full == 2:
1764 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1774 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1765 page(str)
1775 page(str)
1766 return snip
1776 return snip
1767
1777
1768 #****************************************************************************
1778 #****************************************************************************
1769 # lists, dicts and structures
1779 # lists, dicts and structures
1770
1780
1771 def belong(candidates,checklist):
1781 def belong(candidates,checklist):
1772 """Check whether a list of items appear in a given list of options.
1782 """Check whether a list of items appear in a given list of options.
1773
1783
1774 Returns a list of 1 and 0, one for each candidate given."""
1784 Returns a list of 1 and 0, one for each candidate given."""
1775
1785
1776 return [x in checklist for x in candidates]
1786 return [x in checklist for x in candidates]
1777
1787
1778 #----------------------------------------------------------------------------
1788 #----------------------------------------------------------------------------
1779 def uniq_stable(elems):
1789 def uniq_stable(elems):
1780 """uniq_stable(elems) -> list
1790 """uniq_stable(elems) -> list
1781
1791
1782 Return from an iterable, a list of all the unique elements in the input,
1792 Return from an iterable, a list of all the unique elements in the input,
1783 but maintaining the order in which they first appear.
1793 but maintaining the order in which they first appear.
1784
1794
1785 A naive solution to this problem which just makes a dictionary with the
1795 A naive solution to this problem which just makes a dictionary with the
1786 elements as keys fails to respect the stability condition, since
1796 elements as keys fails to respect the stability condition, since
1787 dictionaries are unsorted by nature.
1797 dictionaries are unsorted by nature.
1788
1798
1789 Note: All elements in the input must be valid dictionary keys for this
1799 Note: All elements in the input must be valid dictionary keys for this
1790 routine to work, as it internally uses a dictionary for efficiency
1800 routine to work, as it internally uses a dictionary for efficiency
1791 reasons."""
1801 reasons."""
1792
1802
1793 unique = []
1803 unique = []
1794 unique_dict = {}
1804 unique_dict = {}
1795 for nn in elems:
1805 for nn in elems:
1796 if nn not in unique_dict:
1806 if nn not in unique_dict:
1797 unique.append(nn)
1807 unique.append(nn)
1798 unique_dict[nn] = None
1808 unique_dict[nn] = None
1799 return unique
1809 return unique
1800
1810
1801 #----------------------------------------------------------------------------
1811 #----------------------------------------------------------------------------
1802 class NLprinter:
1812 class NLprinter:
1803 """Print an arbitrarily nested list, indicating index numbers.
1813 """Print an arbitrarily nested list, indicating index numbers.
1804
1814
1805 An instance of this class called nlprint is available and callable as a
1815 An instance of this class called nlprint is available and callable as a
1806 function.
1816 function.
1807
1817
1808 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1818 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1809 and using 'sep' to separate the index from the value. """
1819 and using 'sep' to separate the index from the value. """
1810
1820
1811 def __init__(self):
1821 def __init__(self):
1812 self.depth = 0
1822 self.depth = 0
1813
1823
1814 def __call__(self,lst,pos='',**kw):
1824 def __call__(self,lst,pos='',**kw):
1815 """Prints the nested list numbering levels."""
1825 """Prints the nested list numbering levels."""
1816 kw.setdefault('indent',' ')
1826 kw.setdefault('indent',' ')
1817 kw.setdefault('sep',': ')
1827 kw.setdefault('sep',': ')
1818 kw.setdefault('start',0)
1828 kw.setdefault('start',0)
1819 kw.setdefault('stop',len(lst))
1829 kw.setdefault('stop',len(lst))
1820 # we need to remove start and stop from kw so they don't propagate
1830 # we need to remove start and stop from kw so they don't propagate
1821 # into a recursive call for a nested list.
1831 # into a recursive call for a nested list.
1822 start = kw['start']; del kw['start']
1832 start = kw['start']; del kw['start']
1823 stop = kw['stop']; del kw['stop']
1833 stop = kw['stop']; del kw['stop']
1824 if self.depth == 0 and 'header' in kw.keys():
1834 if self.depth == 0 and 'header' in kw.keys():
1825 print kw['header']
1835 print kw['header']
1826
1836
1827 for idx in range(start,stop):
1837 for idx in range(start,stop):
1828 elem = lst[idx]
1838 elem = lst[idx]
1829 if type(elem)==type([]):
1839 if type(elem)==type([]):
1830 self.depth += 1
1840 self.depth += 1
1831 self.__call__(elem,itpl('$pos$idx,'),**kw)
1841 self.__call__(elem,itpl('$pos$idx,'),**kw)
1832 self.depth -= 1
1842 self.depth -= 1
1833 else:
1843 else:
1834 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1844 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1835
1845
1836 nlprint = NLprinter()
1846 nlprint = NLprinter()
1837 #----------------------------------------------------------------------------
1847 #----------------------------------------------------------------------------
1838 def all_belong(candidates,checklist):
1848 def all_belong(candidates,checklist):
1839 """Check whether a list of items ALL appear in a given list of options.
1849 """Check whether a list of items ALL appear in a given list of options.
1840
1850
1841 Returns a single 1 or 0 value."""
1851 Returns a single 1 or 0 value."""
1842
1852
1843 return 1-(0 in [x in checklist for x in candidates])
1853 return 1-(0 in [x in checklist for x in candidates])
1844
1854
1845 #----------------------------------------------------------------------------
1855 #----------------------------------------------------------------------------
1846 def sort_compare(lst1,lst2,inplace = 1):
1856 def sort_compare(lst1,lst2,inplace = 1):
1847 """Sort and compare two lists.
1857 """Sort and compare two lists.
1848
1858
1849 By default it does it in place, thus modifying the lists. Use inplace = 0
1859 By default it does it in place, thus modifying the lists. Use inplace = 0
1850 to avoid that (at the cost of temporary copy creation)."""
1860 to avoid that (at the cost of temporary copy creation)."""
1851 if not inplace:
1861 if not inplace:
1852 lst1 = lst1[:]
1862 lst1 = lst1[:]
1853 lst2 = lst2[:]
1863 lst2 = lst2[:]
1854 lst1.sort(); lst2.sort()
1864 lst1.sort(); lst2.sort()
1855 return lst1 == lst2
1865 return lst1 == lst2
1856
1866
1857 #----------------------------------------------------------------------------
1867 #----------------------------------------------------------------------------
1858 def list2dict(lst):
1868 def list2dict(lst):
1859 """Takes a list of (key,value) pairs and turns it into a dict."""
1869 """Takes a list of (key,value) pairs and turns it into a dict."""
1860
1870
1861 dic = {}
1871 dic = {}
1862 for k,v in lst: dic[k] = v
1872 for k,v in lst: dic[k] = v
1863 return dic
1873 return dic
1864
1874
1865 #----------------------------------------------------------------------------
1875 #----------------------------------------------------------------------------
1866 def list2dict2(lst,default=''):
1876 def list2dict2(lst,default=''):
1867 """Takes a list and turns it into a dict.
1877 """Takes a list and turns it into a dict.
1868 Much slower than list2dict, but more versatile. This version can take
1878 Much slower than list2dict, but more versatile. This version can take
1869 lists with sublists of arbitrary length (including sclars)."""
1879 lists with sublists of arbitrary length (including sclars)."""
1870
1880
1871 dic = {}
1881 dic = {}
1872 for elem in lst:
1882 for elem in lst:
1873 if type(elem) in (types.ListType,types.TupleType):
1883 if type(elem) in (types.ListType,types.TupleType):
1874 size = len(elem)
1884 size = len(elem)
1875 if size == 0:
1885 if size == 0:
1876 pass
1886 pass
1877 elif size == 1:
1887 elif size == 1:
1878 dic[elem] = default
1888 dic[elem] = default
1879 else:
1889 else:
1880 k,v = elem[0], elem[1:]
1890 k,v = elem[0], elem[1:]
1881 if len(v) == 1: v = v[0]
1891 if len(v) == 1: v = v[0]
1882 dic[k] = v
1892 dic[k] = v
1883 else:
1893 else:
1884 dic[elem] = default
1894 dic[elem] = default
1885 return dic
1895 return dic
1886
1896
1887 #----------------------------------------------------------------------------
1897 #----------------------------------------------------------------------------
1888 def flatten(seq):
1898 def flatten(seq):
1889 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1899 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1890
1900
1891 return [x for subseq in seq for x in subseq]
1901 return [x for subseq in seq for x in subseq]
1892
1902
1893 #----------------------------------------------------------------------------
1903 #----------------------------------------------------------------------------
1894 def get_slice(seq,start=0,stop=None,step=1):
1904 def get_slice(seq,start=0,stop=None,step=1):
1895 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1905 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1896 if stop == None:
1906 if stop == None:
1897 stop = len(seq)
1907 stop = len(seq)
1898 item = lambda i: seq[i]
1908 item = lambda i: seq[i]
1899 return map(item,xrange(start,stop,step))
1909 return map(item,xrange(start,stop,step))
1900
1910
1901 #----------------------------------------------------------------------------
1911 #----------------------------------------------------------------------------
1902 def chop(seq,size):
1912 def chop(seq,size):
1903 """Chop a sequence into chunks of the given size."""
1913 """Chop a sequence into chunks of the given size."""
1904 chunk = lambda i: seq[i:i+size]
1914 chunk = lambda i: seq[i:i+size]
1905 return map(chunk,xrange(0,len(seq),size))
1915 return map(chunk,xrange(0,len(seq),size))
1906
1916
1907 #----------------------------------------------------------------------------
1917 #----------------------------------------------------------------------------
1908 # with is a keyword as of python 2.5, so this function is renamed to withobj
1918 # with is a keyword as of python 2.5, so this function is renamed to withobj
1909 # from its old 'with' name.
1919 # from its old 'with' name.
1910 def with_obj(object, **args):
1920 def with_obj(object, **args):
1911 """Set multiple attributes for an object, similar to Pascal's with.
1921 """Set multiple attributes for an object, similar to Pascal's with.
1912
1922
1913 Example:
1923 Example:
1914 with_obj(jim,
1924 with_obj(jim,
1915 born = 1960,
1925 born = 1960,
1916 haircolour = 'Brown',
1926 haircolour = 'Brown',
1917 eyecolour = 'Green')
1927 eyecolour = 'Green')
1918
1928
1919 Credit: Greg Ewing, in
1929 Credit: Greg Ewing, in
1920 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1930 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1921
1931
1922 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1932 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1923 has become a keyword for Python 2.5, so we had to rename it."""
1933 has become a keyword for Python 2.5, so we had to rename it."""
1924
1934
1925 object.__dict__.update(args)
1935 object.__dict__.update(args)
1926
1936
1927 #----------------------------------------------------------------------------
1937 #----------------------------------------------------------------------------
1928 def setattr_list(obj,alist,nspace = None):
1938 def setattr_list(obj,alist,nspace = None):
1929 """Set a list of attributes for an object taken from a namespace.
1939 """Set a list of attributes for an object taken from a namespace.
1930
1940
1931 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1941 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1932 alist with their values taken from nspace, which must be a dict (something
1942 alist with their values taken from nspace, which must be a dict (something
1933 like locals() will often do) If nspace isn't given, locals() of the
1943 like locals() will often do) If nspace isn't given, locals() of the
1934 *caller* is used, so in most cases you can omit it.
1944 *caller* is used, so in most cases you can omit it.
1935
1945
1936 Note that alist can be given as a string, which will be automatically
1946 Note that alist can be given as a string, which will be automatically
1937 split into a list on whitespace. If given as a list, it must be a list of
1947 split into a list on whitespace. If given as a list, it must be a list of
1938 *strings* (the variable names themselves), not of variables."""
1948 *strings* (the variable names themselves), not of variables."""
1939
1949
1940 # this grabs the local variables from the *previous* call frame -- that is
1950 # this grabs the local variables from the *previous* call frame -- that is
1941 # the locals from the function that called setattr_list().
1951 # the locals from the function that called setattr_list().
1942 # - snipped from weave.inline()
1952 # - snipped from weave.inline()
1943 if nspace is None:
1953 if nspace is None:
1944 call_frame = sys._getframe().f_back
1954 call_frame = sys._getframe().f_back
1945 nspace = call_frame.f_locals
1955 nspace = call_frame.f_locals
1946
1956
1947 if type(alist) in StringTypes:
1957 if type(alist) in StringTypes:
1948 alist = alist.split()
1958 alist = alist.split()
1949 for attr in alist:
1959 for attr in alist:
1950 val = eval(attr,nspace)
1960 val = eval(attr,nspace)
1951 setattr(obj,attr,val)
1961 setattr(obj,attr,val)
1952
1962
1953 #----------------------------------------------------------------------------
1963 #----------------------------------------------------------------------------
1954 def getattr_list(obj,alist,*args):
1964 def getattr_list(obj,alist,*args):
1955 """getattr_list(obj,alist[, default]) -> attribute list.
1965 """getattr_list(obj,alist[, default]) -> attribute list.
1956
1966
1957 Get a list of named attributes for an object. When a default argument is
1967 Get a list of named attributes for an object. When a default argument is
1958 given, it is returned when the attribute doesn't exist; without it, an
1968 given, it is returned when the attribute doesn't exist; without it, an
1959 exception is raised in that case.
1969 exception is raised in that case.
1960
1970
1961 Note that alist can be given as a string, which will be automatically
1971 Note that alist can be given as a string, which will be automatically
1962 split into a list on whitespace. If given as a list, it must be a list of
1972 split into a list on whitespace. If given as a list, it must be a list of
1963 *strings* (the variable names themselves), not of variables."""
1973 *strings* (the variable names themselves), not of variables."""
1964
1974
1965 if type(alist) in StringTypes:
1975 if type(alist) in StringTypes:
1966 alist = alist.split()
1976 alist = alist.split()
1967 if args:
1977 if args:
1968 if len(args)==1:
1978 if len(args)==1:
1969 default = args[0]
1979 default = args[0]
1970 return map(lambda attr: getattr(obj,attr,default),alist)
1980 return map(lambda attr: getattr(obj,attr,default),alist)
1971 else:
1981 else:
1972 raise ValueError,'getattr_list() takes only one optional argument'
1982 raise ValueError,'getattr_list() takes only one optional argument'
1973 else:
1983 else:
1974 return map(lambda attr: getattr(obj,attr),alist)
1984 return map(lambda attr: getattr(obj,attr),alist)
1975
1985
1976 #----------------------------------------------------------------------------
1986 #----------------------------------------------------------------------------
1977 def map_method(method,object_list,*argseq,**kw):
1987 def map_method(method,object_list,*argseq,**kw):
1978 """map_method(method,object_list,*args,**kw) -> list
1988 """map_method(method,object_list,*args,**kw) -> list
1979
1989
1980 Return a list of the results of applying the methods to the items of the
1990 Return a list of the results of applying the methods to the items of the
1981 argument sequence(s). If more than one sequence is given, the method is
1991 argument sequence(s). If more than one sequence is given, the method is
1982 called with an argument list consisting of the corresponding item of each
1992 called with an argument list consisting of the corresponding item of each
1983 sequence. All sequences must be of the same length.
1993 sequence. All sequences must be of the same length.
1984
1994
1985 Keyword arguments are passed verbatim to all objects called.
1995 Keyword arguments are passed verbatim to all objects called.
1986
1996
1987 This is Python code, so it's not nearly as fast as the builtin map()."""
1997 This is Python code, so it's not nearly as fast as the builtin map()."""
1988
1998
1989 out_list = []
1999 out_list = []
1990 idx = 0
2000 idx = 0
1991 for object in object_list:
2001 for object in object_list:
1992 try:
2002 try:
1993 handler = getattr(object, method)
2003 handler = getattr(object, method)
1994 except AttributeError:
2004 except AttributeError:
1995 out_list.append(None)
2005 out_list.append(None)
1996 else:
2006 else:
1997 if argseq:
2007 if argseq:
1998 args = map(lambda lst:lst[idx],argseq)
2008 args = map(lambda lst:lst[idx],argseq)
1999 #print 'ob',object,'hand',handler,'ar',args # dbg
2009 #print 'ob',object,'hand',handler,'ar',args # dbg
2000 out_list.append(handler(args,**kw))
2010 out_list.append(handler(args,**kw))
2001 else:
2011 else:
2002 out_list.append(handler(**kw))
2012 out_list.append(handler(**kw))
2003 idx += 1
2013 idx += 1
2004 return out_list
2014 return out_list
2005
2015
2006 #----------------------------------------------------------------------------
2016 #----------------------------------------------------------------------------
2007 def get_class_members(cls):
2017 def get_class_members(cls):
2008 ret = dir(cls)
2018 ret = dir(cls)
2009 if hasattr(cls,'__bases__'):
2019 if hasattr(cls,'__bases__'):
2010 for base in cls.__bases__:
2020 for base in cls.__bases__:
2011 ret.extend(get_class_members(base))
2021 ret.extend(get_class_members(base))
2012 return ret
2022 return ret
2013
2023
2014 #----------------------------------------------------------------------------
2024 #----------------------------------------------------------------------------
2015 def dir2(obj):
2025 def dir2(obj):
2016 """dir2(obj) -> list of strings
2026 """dir2(obj) -> list of strings
2017
2027
2018 Extended version of the Python builtin dir(), which does a few extra
2028 Extended version of the Python builtin dir(), which does a few extra
2019 checks, and supports common objects with unusual internals that confuse
2029 checks, and supports common objects with unusual internals that confuse
2020 dir(), such as Traits and PyCrust.
2030 dir(), such as Traits and PyCrust.
2021
2031
2022 This version is guaranteed to return only a list of true strings, whereas
2032 This version is guaranteed to return only a list of true strings, whereas
2023 dir() returns anything that objects inject into themselves, even if they
2033 dir() returns anything that objects inject into themselves, even if they
2024 are later not really valid for attribute access (many extension libraries
2034 are later not really valid for attribute access (many extension libraries
2025 have such bugs).
2035 have such bugs).
2026 """
2036 """
2027
2037
2028 # Start building the attribute list via dir(), and then complete it
2038 # Start building the attribute list via dir(), and then complete it
2029 # with a few extra special-purpose calls.
2039 # with a few extra special-purpose calls.
2030 words = dir(obj)
2040 words = dir(obj)
2031
2041
2032 if hasattr(obj,'__class__'):
2042 if hasattr(obj,'__class__'):
2033 words.append('__class__')
2043 words.append('__class__')
2034 words.extend(get_class_members(obj.__class__))
2044 words.extend(get_class_members(obj.__class__))
2035 #if '__base__' in words: 1/0
2045 #if '__base__' in words: 1/0
2036
2046
2037 # Some libraries (such as traits) may introduce duplicates, we want to
2047 # Some libraries (such as traits) may introduce duplicates, we want to
2038 # track and clean this up if it happens
2048 # track and clean this up if it happens
2039 may_have_dupes = False
2049 may_have_dupes = False
2040
2050
2041 # this is the 'dir' function for objects with Enthought's traits
2051 # this is the 'dir' function for objects with Enthought's traits
2042 if hasattr(obj, 'trait_names'):
2052 if hasattr(obj, 'trait_names'):
2043 try:
2053 try:
2044 words.extend(obj.trait_names())
2054 words.extend(obj.trait_names())
2045 may_have_dupes = True
2055 may_have_dupes = True
2046 except TypeError:
2056 except TypeError:
2047 # This will happen if `obj` is a class and not an instance.
2057 # This will happen if `obj` is a class and not an instance.
2048 pass
2058 pass
2049
2059
2050 # Support for PyCrust-style _getAttributeNames magic method.
2060 # Support for PyCrust-style _getAttributeNames magic method.
2051 if hasattr(obj, '_getAttributeNames'):
2061 if hasattr(obj, '_getAttributeNames'):
2052 try:
2062 try:
2053 words.extend(obj._getAttributeNames())
2063 words.extend(obj._getAttributeNames())
2054 may_have_dupes = True
2064 may_have_dupes = True
2055 except TypeError:
2065 except TypeError:
2056 # `obj` is a class and not an instance. Ignore
2066 # `obj` is a class and not an instance. Ignore
2057 # this error.
2067 # this error.
2058 pass
2068 pass
2059
2069
2060 if may_have_dupes:
2070 if may_have_dupes:
2061 # eliminate possible duplicates, as some traits may also
2071 # eliminate possible duplicates, as some traits may also
2062 # appear as normal attributes in the dir() call.
2072 # appear as normal attributes in the dir() call.
2063 words = list(set(words))
2073 words = list(set(words))
2064 words.sort()
2074 words.sort()
2065
2075
2066 # filter out non-string attributes which may be stuffed by dir() calls
2076 # filter out non-string attributes which may be stuffed by dir() calls
2067 # and poor coding in third-party modules
2077 # and poor coding in third-party modules
2068 return [w for w in words if isinstance(w, basestring)]
2078 return [w for w in words if isinstance(w, basestring)]
2069
2079
2070 #----------------------------------------------------------------------------
2080 #----------------------------------------------------------------------------
2071 def import_fail_info(mod_name,fns=None):
2081 def import_fail_info(mod_name,fns=None):
2072 """Inform load failure for a module."""
2082 """Inform load failure for a module."""
2073
2083
2074 if fns == None:
2084 if fns == None:
2075 warn("Loading of %s failed.\n" % (mod_name,))
2085 warn("Loading of %s failed.\n" % (mod_name,))
2076 else:
2086 else:
2077 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2087 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2078
2088
2079 #----------------------------------------------------------------------------
2089 #----------------------------------------------------------------------------
2080 # Proposed popitem() extension, written as a method
2090 # Proposed popitem() extension, written as a method
2081
2091
2082
2092
2083 class NotGiven: pass
2093 class NotGiven: pass
2084
2094
2085 def popkey(dct,key,default=NotGiven):
2095 def popkey(dct,key,default=NotGiven):
2086 """Return dct[key] and delete dct[key].
2096 """Return dct[key] and delete dct[key].
2087
2097
2088 If default is given, return it if dct[key] doesn't exist, otherwise raise
2098 If default is given, return it if dct[key] doesn't exist, otherwise raise
2089 KeyError. """
2099 KeyError. """
2090
2100
2091 try:
2101 try:
2092 val = dct[key]
2102 val = dct[key]
2093 except KeyError:
2103 except KeyError:
2094 if default is NotGiven:
2104 if default is NotGiven:
2095 raise
2105 raise
2096 else:
2106 else:
2097 return default
2107 return default
2098 else:
2108 else:
2099 del dct[key]
2109 del dct[key]
2100 return val
2110 return val
2101
2111
2102 def wrap_deprecated(func, suggest = '<nothing>'):
2112 def wrap_deprecated(func, suggest = '<nothing>'):
2103 def newFunc(*args, **kwargs):
2113 def newFunc(*args, **kwargs):
2104 warnings.warn("Call to deprecated function %s, use %s instead" %
2114 warnings.warn("Call to deprecated function %s, use %s instead" %
2105 ( func.__name__, suggest),
2115 ( func.__name__, suggest),
2106 category=DeprecationWarning,
2116 category=DeprecationWarning,
2107 stacklevel = 2)
2117 stacklevel = 2)
2108 return func(*args, **kwargs)
2118 return func(*args, **kwargs)
2109 return newFunc
2119 return newFunc
2110
2120
2111
2121
2112 def _num_cpus_unix():
2122 def _num_cpus_unix():
2113 """Return the number of active CPUs on a Unix system."""
2123 """Return the number of active CPUs on a Unix system."""
2114 return os.sysconf("SC_NPROCESSORS_ONLN")
2124 return os.sysconf("SC_NPROCESSORS_ONLN")
2115
2125
2116
2126
2117 def _num_cpus_darwin():
2127 def _num_cpus_darwin():
2118 """Return the number of active CPUs on a Darwin system."""
2128 """Return the number of active CPUs on a Darwin system."""
2119 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2129 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2120 return p.stdout.read()
2130 return p.stdout.read()
2121
2131
2122
2132
2123 def _num_cpus_windows():
2133 def _num_cpus_windows():
2124 """Return the number of active CPUs on a Windows system."""
2134 """Return the number of active CPUs on a Windows system."""
2125 return os.environ.get("NUMBER_OF_PROCESSORS")
2135 return os.environ.get("NUMBER_OF_PROCESSORS")
2126
2136
2127
2137
2128 def num_cpus():
2138 def num_cpus():
2129 """Return the effective number of CPUs in the system as an integer.
2139 """Return the effective number of CPUs in the system as an integer.
2130
2140
2131 This cross-platform function makes an attempt at finding the total number of
2141 This cross-platform function makes an attempt at finding the total number of
2132 available CPUs in the system, as returned by various underlying system and
2142 available CPUs in the system, as returned by various underlying system and
2133 python calls.
2143 python calls.
2134
2144
2135 If it can't find a sensible answer, it returns 1 (though an error *may* make
2145 If it can't find a sensible answer, it returns 1 (though an error *may* make
2136 it return a large positive number that's actually incorrect).
2146 it return a large positive number that's actually incorrect).
2137 """
2147 """
2138
2148
2139 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2149 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2140 # for the names of the keys we needed to look up for this function. This
2150 # for the names of the keys we needed to look up for this function. This
2141 # code was inspired by their equivalent function.
2151 # code was inspired by their equivalent function.
2142
2152
2143 ncpufuncs = {'Linux':_num_cpus_unix,
2153 ncpufuncs = {'Linux':_num_cpus_unix,
2144 'Darwin':_num_cpus_darwin,
2154 'Darwin':_num_cpus_darwin,
2145 'Windows':_num_cpus_windows,
2155 'Windows':_num_cpus_windows,
2146 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2156 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2147 # See http://bugs.python.org/issue1082 for details.
2157 # See http://bugs.python.org/issue1082 for details.
2148 'Microsoft':_num_cpus_windows,
2158 'Microsoft':_num_cpus_windows,
2149 }
2159 }
2150
2160
2151 ncpufunc = ncpufuncs.get(platform.system(),
2161 ncpufunc = ncpufuncs.get(platform.system(),
2152 # default to unix version (Solaris, AIX, etc)
2162 # default to unix version (Solaris, AIX, etc)
2153 _num_cpus_unix)
2163 _num_cpus_unix)
2154
2164
2155 try:
2165 try:
2156 ncpus = max(1,int(ncpufunc()))
2166 ncpus = max(1,int(ncpufunc()))
2157 except:
2167 except:
2158 ncpus = 1
2168 ncpus = 1
2159 return ncpus
2169 return ncpus
2160
2170
2161 #*************************** end of file <genutils.py> **********************
2171 #*************************** end of file <genutils.py> **********************
@@ -1,24 +1,23 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """The IPython1 kernel.
2 """The IPython1 kernel.
3
3
4 The IPython kernel actually refers to three things:
4 The IPython kernel actually refers to three things:
5
5
6 * The IPython Engine
6 * The IPython Engine
7 * The IPython Controller
7 * The IPython Controller
8 * Clients to the IPython Controller
8 * Clients to the IPython Controller
9
9
10 The kernel module implements the engine, controller and client and all the
10 The kernel module implements the engine, controller and client and all the
11 network protocols needed for the various entities to talk to each other.
11 network protocols needed for the various entities to talk to each other.
12
12
13 An end user should probably begin by looking at the `client.py` module
13 An end user should probably begin by looking at the `client.py` module
14 if they need blocking clients or in `asyncclient.py` if they want asynchronous,
14 if they need blocking clients or in `asyncclient.py` if they want asynchronous,
15 deferred/Twisted using clients.
15 deferred/Twisted using clients.
16 """
16 """
17 __docformat__ = "restructuredtext en"
17 __docformat__ = "restructuredtext en"
18 #-------------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Copyright (C) 2008 The IPython Development Team
19 # Copyright (C) 2008 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #-------------------------------------------------------------------------------
23 #----------------------------------------------------------------------------- No newline at end of file
24 No newline at end of file
@@ -1,124 +1,126 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Default kernel configuration."""
3 """Default kernel configuration."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import os, sys
18 from os.path import join as pjoin
19 from os.path import join as pjoin
19
20
20 from IPython.external.configobj import ConfigObj
21 from IPython.external.configobj import ConfigObj
21 from IPython.config.api import ConfigObjManager
22 from IPython.config.api import ConfigObjManager
22 from IPython.genutils import get_ipython_dir, get_security_dir
23 from IPython.genutils import get_ipython_dir, get_security_dir
23
24
24 default_kernel_config = ConfigObj()
25 default_kernel_config = ConfigObj()
25
26
27 # This will raise OSError if ipythondir doesn't exist.
26 security_dir = get_security_dir()
28 security_dir = get_security_dir()
27
29
28 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
29 # Engine Configuration
31 # Engine Configuration
30 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
31
33
32 engine_config = dict(
34 engine_config = dict(
33 logfile = '', # Empty means log to stdout
35 logfile = '', # Empty means log to stdout
34 furl_file = pjoin(security_dir, 'ipcontroller-engine.furl')
36 furl_file = pjoin(security_dir, 'ipcontroller-engine.furl')
35 )
37 )
36
38
37 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
38 # MPI Configuration
40 # MPI Configuration
39 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
40
42
41 mpi_config = dict(
43 mpi_config = dict(
42 mpi4py = """from mpi4py import MPI as mpi
44 mpi4py = """from mpi4py import MPI as mpi
43 mpi.size = mpi.COMM_WORLD.Get_size()
45 mpi.size = mpi.COMM_WORLD.Get_size()
44 mpi.rank = mpi.COMM_WORLD.Get_rank()
46 mpi.rank = mpi.COMM_WORLD.Get_rank()
45 """,
47 """,
46 pytrilinos = """from PyTrilinos import Epetra
48 pytrilinos = """from PyTrilinos import Epetra
47 class SimpleStruct:
49 class SimpleStruct:
48 pass
50 pass
49 mpi = SimpleStruct()
51 mpi = SimpleStruct()
50 mpi.rank = 0
52 mpi.rank = 0
51 mpi.size = 0
53 mpi.size = 0
52 """,
54 """,
53 default = ''
55 default = ''
54 )
56 )
55
57
56 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
57 # Controller Configuration
59 # Controller Configuration
58 #-------------------------------------------------------------------------------
60 #-------------------------------------------------------------------------------
59
61
60 controller_config = dict(
62 controller_config = dict(
61
63
62 logfile = '', # Empty means log to stdout
64 logfile = '', # Empty means log to stdout
63 import_statement = '',
65 import_statement = '',
64 reuse_furls = False, # If False, old furl files are deleted
66 reuse_furls = False, # If False, old furl files are deleted
65
67
66 engine_tub = dict(
68 engine_tub = dict(
67 ip = '', # Empty string means all interfaces
69 ip = '', # Empty string means all interfaces
68 port = 0, # 0 means pick a port for me
70 port = 0, # 0 means pick a port for me
69 location = '', # Empty string means try to set automatically
71 location = '', # Empty string means try to set automatically
70 secure = True,
72 secure = True,
71 cert_file = pjoin(security_dir, 'ipcontroller-engine.pem'),
73 cert_file = pjoin(security_dir, 'ipcontroller-engine.pem'),
72 ),
74 ),
73 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
75 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
74 engine_furl_file = pjoin(security_dir, 'ipcontroller-engine.furl'),
76 engine_furl_file = pjoin(security_dir, 'ipcontroller-engine.furl'),
75
77
76 controller_interfaces = dict(
78 controller_interfaces = dict(
77 # multiengine = dict(
79 # multiengine = dict(
78 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
80 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
79 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
81 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
80 # furl_file = 'ipcontroller-mec.furl'
82 # furl_file = 'ipcontroller-mec.furl'
81 # ),
83 # ),
82 task = dict(
84 task = dict(
83 controller_interface = 'IPython.kernel.task.ITaskController',
85 controller_interface = 'IPython.kernel.task.ITaskController',
84 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
86 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
85 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
87 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
86 ),
88 ),
87 multiengine = dict(
89 multiengine = dict(
88 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
90 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
89 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
91 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
90 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
92 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
91 )
93 )
92 ),
94 ),
93
95
94 client_tub = dict(
96 client_tub = dict(
95 ip = '', # Empty string means all interfaces
97 ip = '', # Empty string means all interfaces
96 port = 0, # 0 means pick a port for me
98 port = 0, # 0 means pick a port for me
97 location = '', # Empty string means try to set automatically
99 location = '', # Empty string means try to set automatically
98 secure = True,
100 secure = True,
99 cert_file = pjoin(security_dir, 'ipcontroller-client.pem')
101 cert_file = pjoin(security_dir, 'ipcontroller-client.pem')
100 )
102 )
101 )
103 )
102
104
103 #-------------------------------------------------------------------------------
105 #-------------------------------------------------------------------------------
104 # Client Configuration
106 # Client Configuration
105 #-------------------------------------------------------------------------------
107 #-------------------------------------------------------------------------------
106
108
107 client_config = dict(
109 client_config = dict(
108 client_interfaces = dict(
110 client_interfaces = dict(
109 task = dict(
111 task = dict(
110 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
112 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
111 ),
113 ),
112 multiengine = dict(
114 multiengine = dict(
113 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
115 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
114 )
116 )
115 )
117 )
116 )
118 )
117
119
118 default_kernel_config['engine'] = engine_config
120 default_kernel_config['engine'] = engine_config
119 default_kernel_config['mpi'] = mpi_config
121 default_kernel_config['mpi'] = mpi_config
120 default_kernel_config['controller'] = controller_config
122 default_kernel_config['controller'] = controller_config
121 default_kernel_config['client'] = client_config
123 default_kernel_config['client'] = client_config
122
124
123
125
124 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini') No newline at end of file
126 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini')
@@ -1,801 +1,814 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 """Start an IPython cluster = (controller + engines)."""
4 """Start an IPython cluster = (controller + engines)."""
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
7 # Copyright (C) 2008 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import re
18 import re
19 import sys
19 import sys
20 import signal
20 import signal
21 import tempfile
21 import tempfile
22 pjoin = os.path.join
22 pjoin = os.path.join
23
23
24 from twisted.internet import reactor, defer
24 from twisted.internet import reactor, defer
25 from twisted.internet.protocol import ProcessProtocol
25 from twisted.internet.protocol import ProcessProtocol
26 from twisted.internet.error import ProcessDone, ProcessTerminated
26 from twisted.internet.error import ProcessDone, ProcessTerminated
27 from twisted.internet.utils import getProcessOutput
27 from twisted.internet.utils import getProcessOutput
28 from twisted.python import failure, log
28 from twisted.python import failure, log
29
29
30 from IPython.external import argparse
30 from IPython.external import argparse
31 from IPython.external import Itpl
31 from IPython.external import Itpl
32 from IPython.genutils import get_ipython_dir, num_cpus
32 from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir
33 from IPython.genutils import num_cpus
33 from IPython.kernel.fcutil import have_crypto
34 from IPython.kernel.fcutil import have_crypto
35
36 # Create various ipython directories if they don't exist.
37 # This must be done before IPython.kernel.config is imported.
38 from IPython.iplib import user_setup
39 if os.name == 'posix':
40 rc_suffix = ''
41 else:
42 rc_suffix = '.ini'
43 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
44 get_log_dir()
45 get_security_dir()
46
34 from IPython.kernel.config import config_manager as kernel_config_manager
47 from IPython.kernel.config import config_manager as kernel_config_manager
35 from IPython.kernel.error import SecurityError, FileTimeoutError
48 from IPython.kernel.error import SecurityError, FileTimeoutError
36 from IPython.kernel.fcutil import have_crypto
49 from IPython.kernel.fcutil import have_crypto
37 from IPython.kernel.twistedutil import gatherBoth, wait_for_file
50 from IPython.kernel.twistedutil import gatherBoth, wait_for_file
38 from IPython.kernel.util import printer
51 from IPython.kernel.util import printer
39
52
40
53
41 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
42 # General process handling code
55 # General process handling code
43 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
44
57
45 def find_exe(cmd):
58 def find_exe(cmd):
46 try:
59 try:
47 import win32api
60 import win32api
48 except ImportError:
61 except ImportError:
49 raise ImportError('you need to have pywin32 installed for this to work')
62 raise ImportError('you need to have pywin32 installed for this to work')
50 else:
63 else:
51 try:
64 try:
52 (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe')
65 (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe')
53 except:
66 except:
54 (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat')
67 (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat')
55 return path
68 return path
56
69
57 class ProcessStateError(Exception):
70 class ProcessStateError(Exception):
58 pass
71 pass
59
72
60 class UnknownStatus(Exception):
73 class UnknownStatus(Exception):
61 pass
74 pass
62
75
63 class LauncherProcessProtocol(ProcessProtocol):
76 class LauncherProcessProtocol(ProcessProtocol):
64 """
77 """
65 A ProcessProtocol to go with the ProcessLauncher.
78 A ProcessProtocol to go with the ProcessLauncher.
66 """
79 """
67 def __init__(self, process_launcher):
80 def __init__(self, process_launcher):
68 self.process_launcher = process_launcher
81 self.process_launcher = process_launcher
69
82
70 def connectionMade(self):
83 def connectionMade(self):
71 self.process_launcher.fire_start_deferred(self.transport.pid)
84 self.process_launcher.fire_start_deferred(self.transport.pid)
72
85
73 def processEnded(self, status):
86 def processEnded(self, status):
74 value = status.value
87 value = status.value
75 if isinstance(value, ProcessDone):
88 if isinstance(value, ProcessDone):
76 self.process_launcher.fire_stop_deferred(0)
89 self.process_launcher.fire_stop_deferred(0)
77 elif isinstance(value, ProcessTerminated):
90 elif isinstance(value, ProcessTerminated):
78 self.process_launcher.fire_stop_deferred(
91 self.process_launcher.fire_stop_deferred(
79 {'exit_code':value.exitCode,
92 {'exit_code':value.exitCode,
80 'signal':value.signal,
93 'signal':value.signal,
81 'status':value.status
94 'status':value.status
82 }
95 }
83 )
96 )
84 else:
97 else:
85 raise UnknownStatus("unknown exit status, this is probably a bug in Twisted")
98 raise UnknownStatus("unknown exit status, this is probably a bug in Twisted")
86
99
87 def outReceived(self, data):
100 def outReceived(self, data):
88 log.msg(data)
101 log.msg(data)
89
102
90 def errReceived(self, data):
103 def errReceived(self, data):
91 log.err(data)
104 log.err(data)
92
105
93 class ProcessLauncher(object):
106 class ProcessLauncher(object):
94 """
107 """
95 Start and stop an external process in an asynchronous manner.
108 Start and stop an external process in an asynchronous manner.
96
109
97 Currently this uses deferreds to notify other parties of process state
110 Currently this uses deferreds to notify other parties of process state
98 changes. This is an awkward design and should be moved to using
111 changes. This is an awkward design and should be moved to using
99 a formal NotificationCenter.
112 a formal NotificationCenter.
100 """
113 """
101 def __init__(self, cmd_and_args):
114 def __init__(self, cmd_and_args):
102 self.cmd = cmd_and_args[0]
115 self.cmd = cmd_and_args[0]
103 self.args = cmd_and_args
116 self.args = cmd_and_args
104 self._reset()
117 self._reset()
105
118
106 def _reset(self):
119 def _reset(self):
107 self.process_protocol = None
120 self.process_protocol = None
108 self.pid = None
121 self.pid = None
109 self.start_deferred = None
122 self.start_deferred = None
110 self.stop_deferreds = []
123 self.stop_deferreds = []
111 self.state = 'before' # before, running, or after
124 self.state = 'before' # before, running, or after
112
125
113 @property
126 @property
114 def running(self):
127 def running(self):
115 if self.state == 'running':
128 if self.state == 'running':
116 return True
129 return True
117 else:
130 else:
118 return False
131 return False
119
132
120 def fire_start_deferred(self, pid):
133 def fire_start_deferred(self, pid):
121 self.pid = pid
134 self.pid = pid
122 self.state = 'running'
135 self.state = 'running'
123 log.msg('Process %r has started with pid=%i' % (self.args, pid))
136 log.msg('Process %r has started with pid=%i' % (self.args, pid))
124 self.start_deferred.callback(pid)
137 self.start_deferred.callback(pid)
125
138
126 def start(self):
139 def start(self):
127 if self.state == 'before':
140 if self.state == 'before':
128 self.process_protocol = LauncherProcessProtocol(self)
141 self.process_protocol = LauncherProcessProtocol(self)
129 self.start_deferred = defer.Deferred()
142 self.start_deferred = defer.Deferred()
130 self.process_transport = reactor.spawnProcess(
143 self.process_transport = reactor.spawnProcess(
131 self.process_protocol,
144 self.process_protocol,
132 self.cmd,
145 self.cmd,
133 self.args,
146 self.args,
134 env=os.environ
147 env=os.environ
135 )
148 )
136 return self.start_deferred
149 return self.start_deferred
137 else:
150 else:
138 s = 'the process has already been started and has state: %r' % \
151 s = 'the process has already been started and has state: %r' % \
139 self.state
152 self.state
140 return defer.fail(ProcessStateError(s))
153 return defer.fail(ProcessStateError(s))
141
154
142 def get_stop_deferred(self):
155 def get_stop_deferred(self):
143 if self.state == 'running' or self.state == 'before':
156 if self.state == 'running' or self.state == 'before':
144 d = defer.Deferred()
157 d = defer.Deferred()
145 self.stop_deferreds.append(d)
158 self.stop_deferreds.append(d)
146 return d
159 return d
147 else:
160 else:
148 s = 'this process is already complete'
161 s = 'this process is already complete'
149 return defer.fail(ProcessStateError(s))
162 return defer.fail(ProcessStateError(s))
150
163
151 def fire_stop_deferred(self, exit_code):
164 def fire_stop_deferred(self, exit_code):
152 log.msg('Process %r has stopped with %r' % (self.args, exit_code))
165 log.msg('Process %r has stopped with %r' % (self.args, exit_code))
153 self.state = 'after'
166 self.state = 'after'
154 for d in self.stop_deferreds:
167 for d in self.stop_deferreds:
155 d.callback(exit_code)
168 d.callback(exit_code)
156
169
157 def signal(self, sig):
170 def signal(self, sig):
158 """
171 """
159 Send a signal to the process.
172 Send a signal to the process.
160
173
161 The argument sig can be ('KILL','INT', etc.) or any signal number.
174 The argument sig can be ('KILL','INT', etc.) or any signal number.
162 """
175 """
163 if self.state == 'running':
176 if self.state == 'running':
164 self.process_transport.signalProcess(sig)
177 self.process_transport.signalProcess(sig)
165
178
166 # def __del__(self):
179 # def __del__(self):
167 # self.signal('KILL')
180 # self.signal('KILL')
168
181
169 def interrupt_then_kill(self, delay=1.0):
182 def interrupt_then_kill(self, delay=1.0):
170 self.signal('INT')
183 self.signal('INT')
171 reactor.callLater(delay, self.signal, 'KILL')
184 reactor.callLater(delay, self.signal, 'KILL')
172
185
173
186
174 #-----------------------------------------------------------------------------
187 #-----------------------------------------------------------------------------
175 # Code for launching controller and engines
188 # Code for launching controller and engines
176 #-----------------------------------------------------------------------------
189 #-----------------------------------------------------------------------------
177
190
178
191
179 class ControllerLauncher(ProcessLauncher):
192 class ControllerLauncher(ProcessLauncher):
180
193
181 def __init__(self, extra_args=None):
194 def __init__(self, extra_args=None):
182 if sys.platform == 'win32':
195 if sys.platform == 'win32':
183 # This logic is needed because the ipcontroller script doesn't
196 # This logic is needed because the ipcontroller script doesn't
184 # always get installed in the same way or in the same location.
197 # always get installed in the same way or in the same location.
185 from IPython.kernel.scripts import ipcontroller
198 from IPython.kernel.scripts import ipcontroller
186 script_location = ipcontroller.__file__.replace('.pyc', '.py')
199 script_location = ipcontroller.__file__.replace('.pyc', '.py')
187 # The -u option here turns on unbuffered output, which is required
200 # The -u option here turns on unbuffered output, which is required
188 # on Win32 to prevent wierd conflict and problems with Twisted.
201 # on Win32 to prevent wierd conflict and problems with Twisted.
189 # Also, use sys.executable to make sure we are picking up the
202 # Also, use sys.executable to make sure we are picking up the
190 # right python exe.
203 # right python exe.
191 args = [sys.executable, '-u', script_location]
204 args = [sys.executable, '-u', script_location]
192 else:
205 else:
193 args = ['ipcontroller']
206 args = ['ipcontroller']
194 self.extra_args = extra_args
207 self.extra_args = extra_args
195 if extra_args is not None:
208 if extra_args is not None:
196 args.extend(extra_args)
209 args.extend(extra_args)
197
210
198 ProcessLauncher.__init__(self, args)
211 ProcessLauncher.__init__(self, args)
199
212
200
213
201 class EngineLauncher(ProcessLauncher):
214 class EngineLauncher(ProcessLauncher):
202
215
203 def __init__(self, extra_args=None):
216 def __init__(self, extra_args=None):
204 if sys.platform == 'win32':
217 if sys.platform == 'win32':
205 # This logic is needed because the ipcontroller script doesn't
218 # This logic is needed because the ipcontroller script doesn't
206 # always get installed in the same way or in the same location.
219 # always get installed in the same way or in the same location.
207 from IPython.kernel.scripts import ipengine
220 from IPython.kernel.scripts import ipengine
208 script_location = ipengine.__file__.replace('.pyc', '.py')
221 script_location = ipengine.__file__.replace('.pyc', '.py')
209 # The -u option here turns on unbuffered output, which is required
222 # The -u option here turns on unbuffered output, which is required
210 # on Win32 to prevent wierd conflict and problems with Twisted.
223 # on Win32 to prevent wierd conflict and problems with Twisted.
211 # Also, use sys.executable to make sure we are picking up the
224 # Also, use sys.executable to make sure we are picking up the
212 # right python exe.
225 # right python exe.
213 args = [sys.executable, '-u', script_location]
226 args = [sys.executable, '-u', script_location]
214 else:
227 else:
215 args = ['ipengine']
228 args = ['ipengine']
216 self.extra_args = extra_args
229 self.extra_args = extra_args
217 if extra_args is not None:
230 if extra_args is not None:
218 args.extend(extra_args)
231 args.extend(extra_args)
219
232
220 ProcessLauncher.__init__(self, args)
233 ProcessLauncher.__init__(self, args)
221
234
222
235
223 class LocalEngineSet(object):
236 class LocalEngineSet(object):
224
237
225 def __init__(self, extra_args=None):
238 def __init__(self, extra_args=None):
226 self.extra_args = extra_args
239 self.extra_args = extra_args
227 self.launchers = []
240 self.launchers = []
228
241
229 def start(self, n):
242 def start(self, n):
230 dlist = []
243 dlist = []
231 for i in range(n):
244 for i in range(n):
232 el = EngineLauncher(extra_args=self.extra_args)
245 el = EngineLauncher(extra_args=self.extra_args)
233 d = el.start()
246 d = el.start()
234 self.launchers.append(el)
247 self.launchers.append(el)
235 dlist.append(d)
248 dlist.append(d)
236 dfinal = gatherBoth(dlist, consumeErrors=True)
249 dfinal = gatherBoth(dlist, consumeErrors=True)
237 dfinal.addCallback(self._handle_start)
250 dfinal.addCallback(self._handle_start)
238 return dfinal
251 return dfinal
239
252
240 def _handle_start(self, r):
253 def _handle_start(self, r):
241 log.msg('Engines started with pids: %r' % r)
254 log.msg('Engines started with pids: %r' % r)
242 return r
255 return r
243
256
244 def _handle_stop(self, r):
257 def _handle_stop(self, r):
245 log.msg('Engines received signal: %r' % r)
258 log.msg('Engines received signal: %r' % r)
246 return r
259 return r
247
260
248 def signal(self, sig):
261 def signal(self, sig):
249 dlist = []
262 dlist = []
250 for el in self.launchers:
263 for el in self.launchers:
251 d = el.get_stop_deferred()
264 d = el.get_stop_deferred()
252 dlist.append(d)
265 dlist.append(d)
253 el.signal(sig)
266 el.signal(sig)
254 dfinal = gatherBoth(dlist, consumeErrors=True)
267 dfinal = gatherBoth(dlist, consumeErrors=True)
255 dfinal.addCallback(self._handle_stop)
268 dfinal.addCallback(self._handle_stop)
256 return dfinal
269 return dfinal
257
270
258 def interrupt_then_kill(self, delay=1.0):
271 def interrupt_then_kill(self, delay=1.0):
259 dlist = []
272 dlist = []
260 for el in self.launchers:
273 for el in self.launchers:
261 d = el.get_stop_deferred()
274 d = el.get_stop_deferred()
262 dlist.append(d)
275 dlist.append(d)
263 el.interrupt_then_kill(delay)
276 el.interrupt_then_kill(delay)
264 dfinal = gatherBoth(dlist, consumeErrors=True)
277 dfinal = gatherBoth(dlist, consumeErrors=True)
265 dfinal.addCallback(self._handle_stop)
278 dfinal.addCallback(self._handle_stop)
266 return dfinal
279 return dfinal
267
280
268
281
269 class BatchEngineSet(object):
282 class BatchEngineSet(object):
270
283
271 # Subclasses must fill these in. See PBSEngineSet
284 # Subclasses must fill these in. See PBSEngineSet
272 submit_command = ''
285 submit_command = ''
273 delete_command = ''
286 delete_command = ''
274 job_id_regexp = ''
287 job_id_regexp = ''
275
288
276 def __init__(self, template_file, **kwargs):
289 def __init__(self, template_file, **kwargs):
277 self.template_file = template_file
290 self.template_file = template_file
278 self.context = {}
291 self.context = {}
279 self.context.update(kwargs)
292 self.context.update(kwargs)
280 self.batch_file = self.template_file+'-run'
293 self.batch_file = self.template_file+'-run'
281
294
282 def parse_job_id(self, output):
295 def parse_job_id(self, output):
283 m = re.match(self.job_id_regexp, output)
296 m = re.match(self.job_id_regexp, output)
284 if m is not None:
297 if m is not None:
285 job_id = m.group()
298 job_id = m.group()
286 else:
299 else:
287 raise Exception("job id couldn't be determined: %s" % output)
300 raise Exception("job id couldn't be determined: %s" % output)
288 self.job_id = job_id
301 self.job_id = job_id
289 log.msg('Job started with job id: %r' % job_id)
302 log.msg('Job started with job id: %r' % job_id)
290 return job_id
303 return job_id
291
304
292 def write_batch_script(self, n):
305 def write_batch_script(self, n):
293 self.context['n'] = n
306 self.context['n'] = n
294 template = open(self.template_file, 'r').read()
307 template = open(self.template_file, 'r').read()
295 log.msg('Using template for batch script: %s' % self.template_file)
308 log.msg('Using template for batch script: %s' % self.template_file)
296 script_as_string = Itpl.itplns(template, self.context)
309 script_as_string = Itpl.itplns(template, self.context)
297 log.msg('Writing instantiated batch script: %s' % self.batch_file)
310 log.msg('Writing instantiated batch script: %s' % self.batch_file)
298 f = open(self.batch_file,'w')
311 f = open(self.batch_file,'w')
299 f.write(script_as_string)
312 f.write(script_as_string)
300 f.close()
313 f.close()
301
314
302 def handle_error(self, f):
315 def handle_error(self, f):
303 f.printTraceback()
316 f.printTraceback()
304 f.raiseException()
317 f.raiseException()
305
318
306 def start(self, n):
319 def start(self, n):
307 self.write_batch_script(n)
320 self.write_batch_script(n)
308 d = getProcessOutput(self.submit_command,
321 d = getProcessOutput(self.submit_command,
309 [self.batch_file],env=os.environ)
322 [self.batch_file],env=os.environ)
310 d.addCallback(self.parse_job_id)
323 d.addCallback(self.parse_job_id)
311 d.addErrback(self.handle_error)
324 d.addErrback(self.handle_error)
312 return d
325 return d
313
326
314 def kill(self):
327 def kill(self):
315 d = getProcessOutput(self.delete_command,
328 d = getProcessOutput(self.delete_command,
316 [self.job_id],env=os.environ)
329 [self.job_id],env=os.environ)
317 return d
330 return d
318
331
319 class PBSEngineSet(BatchEngineSet):
332 class PBSEngineSet(BatchEngineSet):
320
333
321 submit_command = 'qsub'
334 submit_command = 'qsub'
322 delete_command = 'qdel'
335 delete_command = 'qdel'
323 job_id_regexp = '\d+'
336 job_id_regexp = '\d+'
324
337
325 def __init__(self, template_file, **kwargs):
338 def __init__(self, template_file, **kwargs):
326 BatchEngineSet.__init__(self, template_file, **kwargs)
339 BatchEngineSet.__init__(self, template_file, **kwargs)
327
340
328
341
329 sshx_template="""#!/bin/sh
342 sshx_template="""#!/bin/sh
330 "$@" &> /dev/null &
343 "$@" &> /dev/null &
331 echo $!
344 echo $!
332 """
345 """
333
346
334 engine_killer_template="""#!/bin/sh
347 engine_killer_template="""#!/bin/sh
335 ps -fu `whoami` | grep '[i]pengine' | awk '{print $2}' | xargs kill -TERM
348 ps -fu `whoami` | grep '[i]pengine' | awk '{print $2}' | xargs kill -TERM
336 """
349 """
337
350
338 class SSHEngineSet(object):
351 class SSHEngineSet(object):
339 sshx_template=sshx_template
352 sshx_template=sshx_template
340 engine_killer_template=engine_killer_template
353 engine_killer_template=engine_killer_template
341
354
342 def __init__(self, engine_hosts, sshx=None, ipengine="ipengine"):
355 def __init__(self, engine_hosts, sshx=None, ipengine="ipengine"):
343 """Start a controller on localhost and engines using ssh.
356 """Start a controller on localhost and engines using ssh.
344
357
345 The engine_hosts argument is a dict with hostnames as keys and
358 The engine_hosts argument is a dict with hostnames as keys and
346 the number of engine (int) as values. sshx is the name of a local
359 the number of engine (int) as values. sshx is the name of a local
347 file that will be used to run remote commands. This file is used
360 file that will be used to run remote commands. This file is used
348 to setup the environment properly.
361 to setup the environment properly.
349 """
362 """
350
363
351 self.temp_dir = tempfile.gettempdir()
364 self.temp_dir = tempfile.gettempdir()
352 if sshx is not None:
365 if sshx is not None:
353 self.sshx = sshx
366 self.sshx = sshx
354 else:
367 else:
355 # Write the sshx.sh file locally from our template.
368 # Write the sshx.sh file locally from our template.
356 self.sshx = os.path.join(
369 self.sshx = os.path.join(
357 self.temp_dir,
370 self.temp_dir,
358 '%s-main-sshx.sh' % os.environ['USER']
371 '%s-main-sshx.sh' % os.environ['USER']
359 )
372 )
360 f = open(self.sshx, 'w')
373 f = open(self.sshx, 'w')
361 f.writelines(self.sshx_template)
374 f.writelines(self.sshx_template)
362 f.close()
375 f.close()
363 self.engine_command = ipengine
376 self.engine_command = ipengine
364 self.engine_hosts = engine_hosts
377 self.engine_hosts = engine_hosts
365 # Write the engine killer script file locally from our template.
378 # Write the engine killer script file locally from our template.
366 self.engine_killer = os.path.join(
379 self.engine_killer = os.path.join(
367 self.temp_dir,
380 self.temp_dir,
368 '%s-local-engine_killer.sh' % os.environ['USER']
381 '%s-local-engine_killer.sh' % os.environ['USER']
369 )
382 )
370 f = open(self.engine_killer, 'w')
383 f = open(self.engine_killer, 'w')
371 f.writelines(self.engine_killer_template)
384 f.writelines(self.engine_killer_template)
372 f.close()
385 f.close()
373
386
374 def start(self, send_furl=False):
387 def start(self, send_furl=False):
375 dlist = []
388 dlist = []
376 for host in self.engine_hosts.keys():
389 for host in self.engine_hosts.keys():
377 count = self.engine_hosts[host]
390 count = self.engine_hosts[host]
378 d = self._start(host, count, send_furl)
391 d = self._start(host, count, send_furl)
379 dlist.append(d)
392 dlist.append(d)
380 return gatherBoth(dlist, consumeErrors=True)
393 return gatherBoth(dlist, consumeErrors=True)
381
394
382 def _start(self, hostname, count=1, send_furl=False):
395 def _start(self, hostname, count=1, send_furl=False):
383 if send_furl:
396 if send_furl:
384 d = self._scp_furl(hostname)
397 d = self._scp_furl(hostname)
385 else:
398 else:
386 d = defer.succeed(None)
399 d = defer.succeed(None)
387 d.addCallback(lambda r: self._scp_sshx(hostname))
400 d.addCallback(lambda r: self._scp_sshx(hostname))
388 d.addCallback(lambda r: self._ssh_engine(hostname, count))
401 d.addCallback(lambda r: self._ssh_engine(hostname, count))
389 return d
402 return d
390
403
391 def _scp_furl(self, hostname):
404 def _scp_furl(self, hostname):
392 scp_cmd = "scp ~/.ipython/security/ipcontroller-engine.furl %s:.ipython/security/" % (hostname)
405 scp_cmd = "scp ~/.ipython/security/ipcontroller-engine.furl %s:.ipython/security/" % (hostname)
393 cmd_list = scp_cmd.split()
406 cmd_list = scp_cmd.split()
394 cmd_list[1] = os.path.expanduser(cmd_list[1])
407 cmd_list[1] = os.path.expanduser(cmd_list[1])
395 log.msg('Copying furl file: %s' % scp_cmd)
408 log.msg('Copying furl file: %s' % scp_cmd)
396 d = getProcessOutput(cmd_list[0], cmd_list[1:], env=os.environ)
409 d = getProcessOutput(cmd_list[0], cmd_list[1:], env=os.environ)
397 return d
410 return d
398
411
399 def _scp_sshx(self, hostname):
412 def _scp_sshx(self, hostname):
400 scp_cmd = "scp %s %s:%s/%s-sshx.sh" % (
413 scp_cmd = "scp %s %s:%s/%s-sshx.sh" % (
401 self.sshx, hostname,
414 self.sshx, hostname,
402 self.temp_dir, os.environ['USER']
415 self.temp_dir, os.environ['USER']
403 )
416 )
404 print
417 print
405 log.msg("Copying sshx: %s" % scp_cmd)
418 log.msg("Copying sshx: %s" % scp_cmd)
406 sshx_scp = scp_cmd.split()
419 sshx_scp = scp_cmd.split()
407 d = getProcessOutput(sshx_scp[0], sshx_scp[1:], env=os.environ)
420 d = getProcessOutput(sshx_scp[0], sshx_scp[1:], env=os.environ)
408 return d
421 return d
409
422
410 def _ssh_engine(self, hostname, count):
423 def _ssh_engine(self, hostname, count):
411 exec_engine = "ssh %s sh %s/%s-sshx.sh %s" % (
424 exec_engine = "ssh %s sh %s/%s-sshx.sh %s" % (
412 hostname, self.temp_dir,
425 hostname, self.temp_dir,
413 os.environ['USER'], self.engine_command
426 os.environ['USER'], self.engine_command
414 )
427 )
415 cmds = exec_engine.split()
428 cmds = exec_engine.split()
416 dlist = []
429 dlist = []
417 log.msg("about to start engines...")
430 log.msg("about to start engines...")
418 for i in range(count):
431 for i in range(count):
419 log.msg('Starting engines: %s' % exec_engine)
432 log.msg('Starting engines: %s' % exec_engine)
420 d = getProcessOutput(cmds[0], cmds[1:], env=os.environ)
433 d = getProcessOutput(cmds[0], cmds[1:], env=os.environ)
421 dlist.append(d)
434 dlist.append(d)
422 return gatherBoth(dlist, consumeErrors=True)
435 return gatherBoth(dlist, consumeErrors=True)
423
436
424 def kill(self):
437 def kill(self):
425 dlist = []
438 dlist = []
426 for host in self.engine_hosts.keys():
439 for host in self.engine_hosts.keys():
427 d = self._killall(host)
440 d = self._killall(host)
428 dlist.append(d)
441 dlist.append(d)
429 return gatherBoth(dlist, consumeErrors=True)
442 return gatherBoth(dlist, consumeErrors=True)
430
443
431 def _killall(self, hostname):
444 def _killall(self, hostname):
432 d = self._scp_engine_killer(hostname)
445 d = self._scp_engine_killer(hostname)
433 d.addCallback(lambda r: self._ssh_kill(hostname))
446 d.addCallback(lambda r: self._ssh_kill(hostname))
434 # d.addErrback(self._exec_err)
447 # d.addErrback(self._exec_err)
435 return d
448 return d
436
449
437 def _scp_engine_killer(self, hostname):
450 def _scp_engine_killer(self, hostname):
438 scp_cmd = "scp %s %s:%s/%s-engine_killer.sh" % (
451 scp_cmd = "scp %s %s:%s/%s-engine_killer.sh" % (
439 self.engine_killer,
452 self.engine_killer,
440 hostname,
453 hostname,
441 self.temp_dir,
454 self.temp_dir,
442 os.environ['USER']
455 os.environ['USER']
443 )
456 )
444 cmds = scp_cmd.split()
457 cmds = scp_cmd.split()
445 log.msg('Copying engine_killer: %s' % scp_cmd)
458 log.msg('Copying engine_killer: %s' % scp_cmd)
446 d = getProcessOutput(cmds[0], cmds[1:], env=os.environ)
459 d = getProcessOutput(cmds[0], cmds[1:], env=os.environ)
447 return d
460 return d
448
461
449 def _ssh_kill(self, hostname):
462 def _ssh_kill(self, hostname):
450 kill_cmd = "ssh %s sh %s/%s-engine_killer.sh" % (
463 kill_cmd = "ssh %s sh %s/%s-engine_killer.sh" % (
451 hostname,
464 hostname,
452 self.temp_dir,
465 self.temp_dir,
453 os.environ['USER']
466 os.environ['USER']
454 )
467 )
455 log.msg('Killing engine: %s' % kill_cmd)
468 log.msg('Killing engine: %s' % kill_cmd)
456 kill_cmd = kill_cmd.split()
469 kill_cmd = kill_cmd.split()
457 d = getProcessOutput(kill_cmd[0], kill_cmd[1:], env=os.environ)
470 d = getProcessOutput(kill_cmd[0], kill_cmd[1:], env=os.environ)
458 return d
471 return d
459
472
460 def _exec_err(self, r):
473 def _exec_err(self, r):
461 log.msg(r)
474 log.msg(r)
462
475
463 #-----------------------------------------------------------------------------
476 #-----------------------------------------------------------------------------
464 # Main functions for the different types of clusters
477 # Main functions for the different types of clusters
465 #-----------------------------------------------------------------------------
478 #-----------------------------------------------------------------------------
466
479
467 # TODO:
480 # TODO:
468 # The logic in these codes should be moved into classes like LocalCluster
481 # The logic in these codes should be moved into classes like LocalCluster
469 # MpirunCluster, PBSCluster, etc. This would remove alot of the duplications.
482 # MpirunCluster, PBSCluster, etc. This would remove alot of the duplications.
470 # The main functions should then just parse the command line arguments, create
483 # The main functions should then just parse the command line arguments, create
471 # the appropriate class and call a 'start' method.
484 # the appropriate class and call a 'start' method.
472
485
473
486
474 def check_security(args, cont_args):
487 def check_security(args, cont_args):
475 if (not args.x or not args.y) and not have_crypto:
488 if (not args.x or not args.y) and not have_crypto:
476 log.err("""
489 log.err("""
477 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
490 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
478 Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""")
491 Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""")
479 reactor.stop()
492 reactor.stop()
480 return False
493 return False
481 if args.x:
494 if args.x:
482 cont_args.append('-x')
495 cont_args.append('-x')
483 if args.y:
496 if args.y:
484 cont_args.append('-y')
497 cont_args.append('-y')
485 return True
498 return True
486
499
487
500
488 def check_reuse(args, cont_args):
501 def check_reuse(args, cont_args):
489 if args.r:
502 if args.r:
490 cont_args.append('-r')
503 cont_args.append('-r')
491 if args.client_port == 0 or args.engine_port == 0:
504 if args.client_port == 0 or args.engine_port == 0:
492 log.err("""
505 log.err("""
493 To reuse FURL files, you must also set the client and engine ports using
506 To reuse FURL files, you must also set the client and engine ports using
494 the --client-port and --engine-port options.""")
507 the --client-port and --engine-port options.""")
495 reactor.stop()
508 reactor.stop()
496 return False
509 return False
497 cont_args.append('--client-port=%i' % args.client_port)
510 cont_args.append('--client-port=%i' % args.client_port)
498 cont_args.append('--engine-port=%i' % args.engine_port)
511 cont_args.append('--engine-port=%i' % args.engine_port)
499 return True
512 return True
500
513
501
514
502 def _err_and_stop(f):
515 def _err_and_stop(f):
503 log.err(f)
516 log.err(f)
504 reactor.stop()
517 reactor.stop()
505
518
506
519
507 def _delay_start(cont_pid, start_engines, furl_file, reuse):
520 def _delay_start(cont_pid, start_engines, furl_file, reuse):
508 if not reuse:
521 if not reuse:
509 if os.path.isfile(furl_file):
522 if os.path.isfile(furl_file):
510 os.unlink(furl_file)
523 os.unlink(furl_file)
511 log.msg('Waiting for controller to finish starting...')
524 log.msg('Waiting for controller to finish starting...')
512 d = wait_for_file(furl_file, delay=0.2, max_tries=50)
525 d = wait_for_file(furl_file, delay=0.2, max_tries=50)
513 d.addCallback(lambda _: log.msg('Controller started'))
526 d.addCallback(lambda _: log.msg('Controller started'))
514 d.addCallback(lambda _: start_engines(cont_pid))
527 d.addCallback(lambda _: start_engines(cont_pid))
515 return d
528 return d
516
529
517
530
518 def main_local(args):
531 def main_local(args):
519 cont_args = []
532 cont_args = []
520 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
533 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
521
534
522 # Check security settings before proceeding
535 # Check security settings before proceeding
523 if not check_security(args, cont_args):
536 if not check_security(args, cont_args):
524 return
537 return
525
538
526 # See if we are reusing FURL files
539 # See if we are reusing FURL files
527 if not check_reuse(args, cont_args):
540 if not check_reuse(args, cont_args):
528 return
541 return
529
542
530 cl = ControllerLauncher(extra_args=cont_args)
543 cl = ControllerLauncher(extra_args=cont_args)
531 dstart = cl.start()
544 dstart = cl.start()
532 def start_engines(cont_pid):
545 def start_engines(cont_pid):
533 engine_args = []
546 engine_args = []
534 engine_args.append('--logfile=%s' % \
547 engine_args.append('--logfile=%s' % \
535 pjoin(args.logdir,'ipengine%s-' % cont_pid))
548 pjoin(args.logdir,'ipengine%s-' % cont_pid))
536 eset = LocalEngineSet(extra_args=engine_args)
549 eset = LocalEngineSet(extra_args=engine_args)
537 def shutdown(signum, frame):
550 def shutdown(signum, frame):
538 log.msg('Stopping local cluster')
551 log.msg('Stopping local cluster')
539 # We are still playing with the times here, but these seem
552 # We are still playing with the times here, but these seem
540 # to be reliable in allowing everything to exit cleanly.
553 # to be reliable in allowing everything to exit cleanly.
541 eset.interrupt_then_kill(0.5)
554 eset.interrupt_then_kill(0.5)
542 cl.interrupt_then_kill(0.5)
555 cl.interrupt_then_kill(0.5)
543 reactor.callLater(1.0, reactor.stop)
556 reactor.callLater(1.0, reactor.stop)
544 signal.signal(signal.SIGINT,shutdown)
557 signal.signal(signal.SIGINT,shutdown)
545 d = eset.start(args.n)
558 d = eset.start(args.n)
546 return d
559 return d
547 config = kernel_config_manager.get_config_obj()
560 config = kernel_config_manager.get_config_obj()
548 furl_file = config['controller']['engine_furl_file']
561 furl_file = config['controller']['engine_furl_file']
549 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
562 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
550 dstart.addErrback(_err_and_stop)
563 dstart.addErrback(_err_and_stop)
551
564
552
565
553 def main_mpi(args):
566 def main_mpi(args):
554 cont_args = []
567 cont_args = []
555 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
568 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
556
569
557 # Check security settings before proceeding
570 # Check security settings before proceeding
558 if not check_security(args, cont_args):
571 if not check_security(args, cont_args):
559 return
572 return
560
573
561 # See if we are reusing FURL files
574 # See if we are reusing FURL files
562 if not check_reuse(args, cont_args):
575 if not check_reuse(args, cont_args):
563 return
576 return
564
577
565 cl = ControllerLauncher(extra_args=cont_args)
578 cl = ControllerLauncher(extra_args=cont_args)
566 dstart = cl.start()
579 dstart = cl.start()
567 def start_engines(cont_pid):
580 def start_engines(cont_pid):
568 raw_args = [args.cmd]
581 raw_args = [args.cmd]
569 raw_args.extend(['-n',str(args.n)])
582 raw_args.extend(['-n',str(args.n)])
570 raw_args.append('ipengine')
583 raw_args.append('ipengine')
571 raw_args.append('-l')
584 raw_args.append('-l')
572 raw_args.append(pjoin(args.logdir,'ipengine%s-' % cont_pid))
585 raw_args.append(pjoin(args.logdir,'ipengine%s-' % cont_pid))
573 if args.mpi:
586 if args.mpi:
574 raw_args.append('--mpi=%s' % args.mpi)
587 raw_args.append('--mpi=%s' % args.mpi)
575 eset = ProcessLauncher(raw_args)
588 eset = ProcessLauncher(raw_args)
576 def shutdown(signum, frame):
589 def shutdown(signum, frame):
577 log.msg('Stopping local cluster')
590 log.msg('Stopping local cluster')
578 # We are still playing with the times here, but these seem
591 # We are still playing with the times here, but these seem
579 # to be reliable in allowing everything to exit cleanly.
592 # to be reliable in allowing everything to exit cleanly.
580 eset.interrupt_then_kill(1.0)
593 eset.interrupt_then_kill(1.0)
581 cl.interrupt_then_kill(1.0)
594 cl.interrupt_then_kill(1.0)
582 reactor.callLater(2.0, reactor.stop)
595 reactor.callLater(2.0, reactor.stop)
583 signal.signal(signal.SIGINT,shutdown)
596 signal.signal(signal.SIGINT,shutdown)
584 d = eset.start()
597 d = eset.start()
585 return d
598 return d
586 config = kernel_config_manager.get_config_obj()
599 config = kernel_config_manager.get_config_obj()
587 furl_file = config['controller']['engine_furl_file']
600 furl_file = config['controller']['engine_furl_file']
588 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
601 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
589 dstart.addErrback(_err_and_stop)
602 dstart.addErrback(_err_and_stop)
590
603
591
604
592 def main_pbs(args):
605 def main_pbs(args):
593 cont_args = []
606 cont_args = []
594 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
607 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
595
608
596 # Check security settings before proceeding
609 # Check security settings before proceeding
597 if not check_security(args, cont_args):
610 if not check_security(args, cont_args):
598 return
611 return
599
612
600 # See if we are reusing FURL files
613 # See if we are reusing FURL files
601 if not check_reuse(args, cont_args):
614 if not check_reuse(args, cont_args):
602 return
615 return
603
616
604 cl = ControllerLauncher(extra_args=cont_args)
617 cl = ControllerLauncher(extra_args=cont_args)
605 dstart = cl.start()
618 dstart = cl.start()
606 def start_engines(r):
619 def start_engines(r):
607 pbs_set = PBSEngineSet(args.pbsscript)
620 pbs_set = PBSEngineSet(args.pbsscript)
608 def shutdown(signum, frame):
621 def shutdown(signum, frame):
609 log.msg('Stopping pbs cluster')
622 log.msg('Stopping pbs cluster')
610 d = pbs_set.kill()
623 d = pbs_set.kill()
611 d.addBoth(lambda _: cl.interrupt_then_kill(1.0))
624 d.addBoth(lambda _: cl.interrupt_then_kill(1.0))
612 d.addBoth(lambda _: reactor.callLater(2.0, reactor.stop))
625 d.addBoth(lambda _: reactor.callLater(2.0, reactor.stop))
613 signal.signal(signal.SIGINT,shutdown)
626 signal.signal(signal.SIGINT,shutdown)
614 d = pbs_set.start(args.n)
627 d = pbs_set.start(args.n)
615 return d
628 return d
616 config = kernel_config_manager.get_config_obj()
629 config = kernel_config_manager.get_config_obj()
617 furl_file = config['controller']['engine_furl_file']
630 furl_file = config['controller']['engine_furl_file']
618 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
631 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
619 dstart.addErrback(_err_and_stop)
632 dstart.addErrback(_err_and_stop)
620
633
621
634
622 def main_ssh(args):
635 def main_ssh(args):
623 """Start a controller on localhost and engines using ssh.
636 """Start a controller on localhost and engines using ssh.
624
637
625 Your clusterfile should look like::
638 Your clusterfile should look like::
626
639
627 send_furl = False # True, if you want
640 send_furl = False # True, if you want
628 engines = {
641 engines = {
629 'engine_host1' : engine_count,
642 'engine_host1' : engine_count,
630 'engine_host2' : engine_count2
643 'engine_host2' : engine_count2
631 }
644 }
632 """
645 """
633 clusterfile = {}
646 clusterfile = {}
634 execfile(args.clusterfile, clusterfile)
647 execfile(args.clusterfile, clusterfile)
635 if not clusterfile.has_key('send_furl'):
648 if not clusterfile.has_key('send_furl'):
636 clusterfile['send_furl'] = False
649 clusterfile['send_furl'] = False
637
650
638 cont_args = []
651 cont_args = []
639 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
652 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
640
653
641 # Check security settings before proceeding
654 # Check security settings before proceeding
642 if not check_security(args, cont_args):
655 if not check_security(args, cont_args):
643 return
656 return
644
657
645 # See if we are reusing FURL files
658 # See if we are reusing FURL files
646 if not check_reuse(args, cont_args):
659 if not check_reuse(args, cont_args):
647 return
660 return
648
661
649 cl = ControllerLauncher(extra_args=cont_args)
662 cl = ControllerLauncher(extra_args=cont_args)
650 dstart = cl.start()
663 dstart = cl.start()
651 def start_engines(cont_pid):
664 def start_engines(cont_pid):
652 ssh_set = SSHEngineSet(clusterfile['engines'], sshx=args.sshx)
665 ssh_set = SSHEngineSet(clusterfile['engines'], sshx=args.sshx)
653 def shutdown(signum, frame):
666 def shutdown(signum, frame):
654 d = ssh_set.kill()
667 d = ssh_set.kill()
655 cl.interrupt_then_kill(1.0)
668 cl.interrupt_then_kill(1.0)
656 reactor.callLater(2.0, reactor.stop)
669 reactor.callLater(2.0, reactor.stop)
657 signal.signal(signal.SIGINT,shutdown)
670 signal.signal(signal.SIGINT,shutdown)
658 d = ssh_set.start(clusterfile['send_furl'])
671 d = ssh_set.start(clusterfile['send_furl'])
659 return d
672 return d
660 config = kernel_config_manager.get_config_obj()
673 config = kernel_config_manager.get_config_obj()
661 furl_file = config['controller']['engine_furl_file']
674 furl_file = config['controller']['engine_furl_file']
662 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
675 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
663 dstart.addErrback(_err_and_stop)
676 dstart.addErrback(_err_and_stop)
664
677
665
678
666 def get_args():
679 def get_args():
667 base_parser = argparse.ArgumentParser(add_help=False)
680 base_parser = argparse.ArgumentParser(add_help=False)
668 base_parser.add_argument(
681 base_parser.add_argument(
669 '-r',
682 '-r',
670 action='store_true',
683 action='store_true',
671 dest='r',
684 dest='r',
672 help='try to reuse FURL files. Use with --client-port and --engine-port'
685 help='try to reuse FURL files. Use with --client-port and --engine-port'
673 )
686 )
674 base_parser.add_argument(
687 base_parser.add_argument(
675 '--client-port',
688 '--client-port',
676 type=int,
689 type=int,
677 dest='client_port',
690 dest='client_port',
678 help='the port the controller will listen on for client connections',
691 help='the port the controller will listen on for client connections',
679 default=0
692 default=0
680 )
693 )
681 base_parser.add_argument(
694 base_parser.add_argument(
682 '--engine-port',
695 '--engine-port',
683 type=int,
696 type=int,
684 dest='engine_port',
697 dest='engine_port',
685 help='the port the controller will listen on for engine connections',
698 help='the port the controller will listen on for engine connections',
686 default=0
699 default=0
687 )
700 )
688 base_parser.add_argument(
701 base_parser.add_argument(
689 '-x',
702 '-x',
690 action='store_true',
703 action='store_true',
691 dest='x',
704 dest='x',
692 help='turn off client security'
705 help='turn off client security'
693 )
706 )
694 base_parser.add_argument(
707 base_parser.add_argument(
695 '-y',
708 '-y',
696 action='store_true',
709 action='store_true',
697 dest='y',
710 dest='y',
698 help='turn off engine security'
711 help='turn off engine security'
699 )
712 )
700 base_parser.add_argument(
713 base_parser.add_argument(
701 "--logdir",
714 "--logdir",
702 type=str,
715 type=str,
703 dest="logdir",
716 dest="logdir",
704 help="directory to put log files (default=$IPYTHONDIR/log)",
717 help="directory to put log files (default=$IPYTHONDIR/log)",
705 default=pjoin(get_ipython_dir(),'log')
718 default=pjoin(get_ipython_dir(),'log')
706 )
719 )
707 base_parser.add_argument(
720 base_parser.add_argument(
708 "-n",
721 "-n",
709 "--num",
722 "--num",
710 type=int,
723 type=int,
711 dest="n",
724 dest="n",
712 default=2,
725 default=2,
713 help="the number of engines to start"
726 help="the number of engines to start"
714 )
727 )
715
728
716 parser = argparse.ArgumentParser(
729 parser = argparse.ArgumentParser(
717 description='IPython cluster startup. This starts a controller and\
730 description='IPython cluster startup. This starts a controller and\
718 engines using various approaches. THIS IS A TECHNOLOGY PREVIEW AND\
731 engines using various approaches. THIS IS A TECHNOLOGY PREVIEW AND\
719 THE API WILL CHANGE SIGNIFICANTLY BEFORE THE FINAL RELEASE.'
732 THE API WILL CHANGE SIGNIFICANTLY BEFORE THE FINAL RELEASE.'
720 )
733 )
721 subparsers = parser.add_subparsers(
734 subparsers = parser.add_subparsers(
722 help='available cluster types. For help, do "ipcluster TYPE --help"')
735 help='available cluster types. For help, do "ipcluster TYPE --help"')
723
736
724 parser_local = subparsers.add_parser(
737 parser_local = subparsers.add_parser(
725 'local',
738 'local',
726 help='run a local cluster',
739 help='run a local cluster',
727 parents=[base_parser]
740 parents=[base_parser]
728 )
741 )
729 parser_local.set_defaults(func=main_local)
742 parser_local.set_defaults(func=main_local)
730
743
731 parser_mpirun = subparsers.add_parser(
744 parser_mpirun = subparsers.add_parser(
732 'mpirun',
745 'mpirun',
733 help='run a cluster using mpirun (mpiexec also works)',
746 help='run a cluster using mpirun (mpiexec also works)',
734 parents=[base_parser]
747 parents=[base_parser]
735 )
748 )
736 parser_mpirun.add_argument(
749 parser_mpirun.add_argument(
737 "--mpi",
750 "--mpi",
738 type=str,
751 type=str,
739 dest="mpi", # Don't put a default here to allow no MPI support
752 dest="mpi", # Don't put a default here to allow no MPI support
740 help="how to call MPI_Init (default=mpi4py)"
753 help="how to call MPI_Init (default=mpi4py)"
741 )
754 )
742 parser_mpirun.set_defaults(func=main_mpi, cmd='mpirun')
755 parser_mpirun.set_defaults(func=main_mpi, cmd='mpirun')
743
756
744 parser_mpiexec = subparsers.add_parser(
757 parser_mpiexec = subparsers.add_parser(
745 'mpiexec',
758 'mpiexec',
746 help='run a cluster using mpiexec (mpirun also works)',
759 help='run a cluster using mpiexec (mpirun also works)',
747 parents=[base_parser]
760 parents=[base_parser]
748 )
761 )
749 parser_mpiexec.add_argument(
762 parser_mpiexec.add_argument(
750 "--mpi",
763 "--mpi",
751 type=str,
764 type=str,
752 dest="mpi", # Don't put a default here to allow no MPI support
765 dest="mpi", # Don't put a default here to allow no MPI support
753 help="how to call MPI_Init (default=mpi4py)"
766 help="how to call MPI_Init (default=mpi4py)"
754 )
767 )
755 parser_mpiexec.set_defaults(func=main_mpi, cmd='mpiexec')
768 parser_mpiexec.set_defaults(func=main_mpi, cmd='mpiexec')
756
769
757 parser_pbs = subparsers.add_parser(
770 parser_pbs = subparsers.add_parser(
758 'pbs',
771 'pbs',
759 help='run a pbs cluster',
772 help='run a pbs cluster',
760 parents=[base_parser]
773 parents=[base_parser]
761 )
774 )
762 parser_pbs.add_argument(
775 parser_pbs.add_argument(
763 '--pbs-script',
776 '--pbs-script',
764 type=str,
777 type=str,
765 dest='pbsscript',
778 dest='pbsscript',
766 help='PBS script template',
779 help='PBS script template',
767 default='pbs.template'
780 default='pbs.template'
768 )
781 )
769 parser_pbs.set_defaults(func=main_pbs)
782 parser_pbs.set_defaults(func=main_pbs)
770
783
771 parser_ssh = subparsers.add_parser(
784 parser_ssh = subparsers.add_parser(
772 'ssh',
785 'ssh',
773 help='run a cluster using ssh, should have ssh-keys setup',
786 help='run a cluster using ssh, should have ssh-keys setup',
774 parents=[base_parser]
787 parents=[base_parser]
775 )
788 )
776 parser_ssh.add_argument(
789 parser_ssh.add_argument(
777 '--clusterfile',
790 '--clusterfile',
778 type=str,
791 type=str,
779 dest='clusterfile',
792 dest='clusterfile',
780 help='python file describing the cluster',
793 help='python file describing the cluster',
781 default='clusterfile.py',
794 default='clusterfile.py',
782 )
795 )
783 parser_ssh.add_argument(
796 parser_ssh.add_argument(
784 '--sshx',
797 '--sshx',
785 type=str,
798 type=str,
786 dest='sshx',
799 dest='sshx',
787 help='sshx launcher helper'
800 help='sshx launcher helper'
788 )
801 )
789 parser_ssh.set_defaults(func=main_ssh)
802 parser_ssh.set_defaults(func=main_ssh)
790
803
791 args = parser.parse_args()
804 args = parser.parse_args()
792 return args
805 return args
793
806
794 def main():
807 def main():
795 args = get_args()
808 args = get_args()
796 reactor.callWhenRunning(args.func, args)
809 reactor.callWhenRunning(args.func, args)
797 log.startLogging(sys.stdout)
810 log.startLogging(sys.stdout)
798 reactor.run()
811 reactor.run()
799
812
800 if __name__ == '__main__':
813 if __name__ == '__main__':
801 main()
814 main()
@@ -1,405 +1,408 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 """The IPython controller."""
4 """The IPython controller."""
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 # Python looks for an empty string at the beginning of sys.path to enable
19 # Python looks for an empty string at the beginning of sys.path to enable
20 # importing from the cwd.
20 # importing from the cwd.
21 import sys
21 import sys
22 sys.path.insert(0, '')
22 sys.path.insert(0, '')
23
23
24 import sys, time, os
24 import sys, time, os
25 import tempfile
25 import tempfile
26 from optparse import OptionParser
26 from optparse import OptionParser
27
27
28 from twisted.application import internet, service
28 from twisted.application import internet, service
29 from twisted.internet import reactor, error, defer
29 from twisted.internet import reactor, error, defer
30 from twisted.python import log
30 from twisted.python import log
31
31
32 from IPython.kernel.fcutil import Tub, UnauthenticatedTub, have_crypto
32 from IPython.kernel.fcutil import Tub, UnauthenticatedTub, have_crypto
33
33
34 # from IPython.tools import growl
34 # from IPython.tools import growl
35 # growl.start("IPython1 Controller")
35 # growl.start("IPython1 Controller")
36
36
37 from IPython.kernel.error import SecurityError
37 from IPython.kernel.error import SecurityError
38 from IPython.kernel import controllerservice
38 from IPython.kernel import controllerservice
39 from IPython.kernel.fcutil import check_furl_file_security
39 from IPython.kernel.fcutil import check_furl_file_security
40
40
41 # Create various ipython directories if they don't exist.
42 # This must be done before IPython.kernel.config is imported.
43 from IPython.iplib import user_setup
44 from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir
45 if os.name == 'posix':
46 rc_suffix = ''
47 else:
48 rc_suffix = '.ini'
49 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
50 get_log_dir()
51 get_security_dir()
52
41 from IPython.kernel.config import config_manager as kernel_config_manager
53 from IPython.kernel.config import config_manager as kernel_config_manager
42 from IPython.config.cutils import import_item
54 from IPython.config.cutils import import_item
43
55
44
56
45 #-------------------------------------------------------------------------------
57 #-------------------------------------------------------------------------------
46 # Code
58 # Code
47 #-------------------------------------------------------------------------------
59 #-------------------------------------------------------------------------------
48
60
49 def get_temp_furlfile(filename):
61 def get_temp_furlfile(filename):
50 return tempfile.mktemp(dir=os.path.dirname(filename),
62 return tempfile.mktemp(dir=os.path.dirname(filename),
51 prefix=os.path.basename(filename))
63 prefix=os.path.basename(filename))
52
64
53 def make_tub(ip, port, secure, cert_file):
65 def make_tub(ip, port, secure, cert_file):
54 """
66 """
55 Create a listening tub given an ip, port, and cert_file location.
67 Create a listening tub given an ip, port, and cert_file location.
56
68
57 :Parameters:
69 :Parameters:
58 ip : str
70 ip : str
59 The ip address that the tub should listen on. Empty means all
71 The ip address that the tub should listen on. Empty means all
60 port : int
72 port : int
61 The port that the tub should listen on. A value of 0 means
73 The port that the tub should listen on. A value of 0 means
62 pick a random port
74 pick a random port
63 secure: boolean
75 secure: boolean
64 Will the connection be secure (in the foolscap sense)
76 Will the connection be secure (in the foolscap sense)
65 cert_file:
77 cert_file:
66 A filename of a file to be used for theSSL certificate
78 A filename of a file to be used for theSSL certificate
67 """
79 """
68 if secure:
80 if secure:
69 if have_crypto:
81 if have_crypto:
70 tub = Tub(certFile=cert_file)
82 tub = Tub(certFile=cert_file)
71 else:
83 else:
72 raise SecurityError("""
84 raise SecurityError("""
73 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
85 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
74 Try running without security using 'ipcontroller -xy'.
86 Try running without security using 'ipcontroller -xy'.
75 """)
87 """)
76 else:
88 else:
77 tub = UnauthenticatedTub()
89 tub = UnauthenticatedTub()
78
90
79 # Set the strport based on the ip and port and start listening
91 # Set the strport based on the ip and port and start listening
80 if ip == '':
92 if ip == '':
81 strport = "tcp:%i" % port
93 strport = "tcp:%i" % port
82 else:
94 else:
83 strport = "tcp:%i:interface=%s" % (port, ip)
95 strport = "tcp:%i:interface=%s" % (port, ip)
84 listener = tub.listenOn(strport)
96 listener = tub.listenOn(strport)
85
97
86 return tub, listener
98 return tub, listener
87
99
88 def make_client_service(controller_service, config):
100 def make_client_service(controller_service, config):
89 """
101 """
90 Create a service that will listen for clients.
102 Create a service that will listen for clients.
91
103
92 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
104 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
93 registered with it.
105 registered with it.
94 """
106 """
95
107
96 # Now create the foolscap tub
108 # Now create the foolscap tub
97 ip = config['controller']['client_tub']['ip']
109 ip = config['controller']['client_tub']['ip']
98 port = config['controller']['client_tub'].as_int('port')
110 port = config['controller']['client_tub'].as_int('port')
99 location = config['controller']['client_tub']['location']
111 location = config['controller']['client_tub']['location']
100 secure = config['controller']['client_tub']['secure']
112 secure = config['controller']['client_tub']['secure']
101 cert_file = config['controller']['client_tub']['cert_file']
113 cert_file = config['controller']['client_tub']['cert_file']
102 client_tub, client_listener = make_tub(ip, port, secure, cert_file)
114 client_tub, client_listener = make_tub(ip, port, secure, cert_file)
103
115
104 # Set the location in the trivial case of localhost
116 # Set the location in the trivial case of localhost
105 if ip == 'localhost' or ip == '127.0.0.1':
117 if ip == 'localhost' or ip == '127.0.0.1':
106 location = "127.0.0.1"
118 location = "127.0.0.1"
107
119
108 if not secure:
120 if not secure:
109 log.msg("WARNING: you are running the controller with no client security")
121 log.msg("WARNING: you are running the controller with no client security")
110
122
111 def set_location_and_register():
123 def set_location_and_register():
112 """Set the location for the tub and return a deferred."""
124 """Set the location for the tub and return a deferred."""
113
125
114 def register(empty, ref, furl_file):
126 def register(empty, ref, furl_file):
115 # We create and then move to make sure that when the file
127 # We create and then move to make sure that when the file
116 # appears to other processes, the buffer has the flushed
128 # appears to other processes, the buffer has the flushed
117 # and the file has been closed
129 # and the file has been closed
118 temp_furl_file = get_temp_furlfile(furl_file)
130 temp_furl_file = get_temp_furlfile(furl_file)
119 log.msg(temp_furl_file)
120 client_tub.registerReference(ref, furlFile=temp_furl_file)
131 client_tub.registerReference(ref, furlFile=temp_furl_file)
121 os.rename(temp_furl_file, furl_file)
132 os.rename(temp_furl_file, furl_file)
122
133
123 if location == '':
134 if location == '':
124 d = client_tub.setLocationAutomatically()
135 d = client_tub.setLocationAutomatically()
125 else:
136 else:
126 d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum()))
137 d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum()))
127
138
128 for ciname, ci in config['controller']['controller_interfaces'].iteritems():
139 for ciname, ci in config['controller']['controller_interfaces'].iteritems():
129 log.msg("Adapting Controller to interface: %s" % ciname)
140 log.msg("Adapting Controller to interface: %s" % ciname)
130 furl_file = ci['furl_file']
141 furl_file = ci['furl_file']
131 log.msg("Saving furl for interface [%s] to file: %s" % (ciname, furl_file))
142 log.msg("Saving furl for interface [%s] to file: %s" % (ciname, furl_file))
132 check_furl_file_security(furl_file, secure)
143 check_furl_file_security(furl_file, secure)
133 adapted_controller = import_item(ci['controller_interface'])(controller_service)
144 adapted_controller = import_item(ci['controller_interface'])(controller_service)
134 d.addCallback(register, import_item(ci['fc_interface'])(adapted_controller),
145 d.addCallback(register, import_item(ci['fc_interface'])(adapted_controller),
135 furl_file=ci['furl_file'])
146 furl_file=ci['furl_file'])
136
147
137 reactor.callWhenRunning(set_location_and_register)
148 reactor.callWhenRunning(set_location_and_register)
138 return client_tub
149 return client_tub
139
150
140
151
141 def make_engine_service(controller_service, config):
152 def make_engine_service(controller_service, config):
142 """
153 """
143 Create a service that will listen for engines.
154 Create a service that will listen for engines.
144
155
145 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
156 This service is simply a `foolscap.Tub` instance that has a set of Referenceables
146 registered with it.
157 registered with it.
147 """
158 """
148
159
149 # Now create the foolscap tub
160 # Now create the foolscap tub
150 ip = config['controller']['engine_tub']['ip']
161 ip = config['controller']['engine_tub']['ip']
151 port = config['controller']['engine_tub'].as_int('port')
162 port = config['controller']['engine_tub'].as_int('port')
152 location = config['controller']['engine_tub']['location']
163 location = config['controller']['engine_tub']['location']
153 secure = config['controller']['engine_tub']['secure']
164 secure = config['controller']['engine_tub']['secure']
154 cert_file = config['controller']['engine_tub']['cert_file']
165 cert_file = config['controller']['engine_tub']['cert_file']
155 engine_tub, engine_listener = make_tub(ip, port, secure, cert_file)
166 engine_tub, engine_listener = make_tub(ip, port, secure, cert_file)
156
167
157 # Set the location in the trivial case of localhost
168 # Set the location in the trivial case of localhost
158 if ip == 'localhost' or ip == '127.0.0.1':
169 if ip == 'localhost' or ip == '127.0.0.1':
159 location = "127.0.0.1"
170 location = "127.0.0.1"
160
171
161 if not secure:
172 if not secure:
162 log.msg("WARNING: you are running the controller with no engine security")
173 log.msg("WARNING: you are running the controller with no engine security")
163
174
164 def set_location_and_register():
175 def set_location_and_register():
165 """Set the location for the tub and return a deferred."""
176 """Set the location for the tub and return a deferred."""
166
177
167 def register(empty, ref, furl_file):
178 def register(empty, ref, furl_file):
168 # We create and then move to make sure that when the file
179 # We create and then move to make sure that when the file
169 # appears to other processes, the buffer has the flushed
180 # appears to other processes, the buffer has the flushed
170 # and the file has been closed
181 # and the file has been closed
171 temp_furl_file = get_temp_furlfile(furl_file)
182 temp_furl_file = get_temp_furlfile(furl_file)
172 log.msg(temp_furl_file)
173 engine_tub.registerReference(ref, furlFile=temp_furl_file)
183 engine_tub.registerReference(ref, furlFile=temp_furl_file)
174 os.rename(temp_furl_file, furl_file)
184 os.rename(temp_furl_file, furl_file)
175
185
176 if location == '':
186 if location == '':
177 d = engine_tub.setLocationAutomatically()
187 d = engine_tub.setLocationAutomatically()
178 else:
188 else:
179 d = defer.maybeDeferred(engine_tub.setLocation, "%s:%i" % (location, engine_listener.getPortnum()))
189 d = defer.maybeDeferred(engine_tub.setLocation, "%s:%i" % (location, engine_listener.getPortnum()))
180
190
181 furl_file = config['controller']['engine_furl_file']
191 furl_file = config['controller']['engine_furl_file']
182 engine_fc_interface = import_item(config['controller']['engine_fc_interface'])
192 engine_fc_interface = import_item(config['controller']['engine_fc_interface'])
183 log.msg("Saving furl for the engine to file: %s" % furl_file)
193 log.msg("Saving furl for the engine to file: %s" % furl_file)
184 check_furl_file_security(furl_file, secure)
194 check_furl_file_security(furl_file, secure)
185 fc_controller = engine_fc_interface(controller_service)
195 fc_controller = engine_fc_interface(controller_service)
186 d.addCallback(register, fc_controller, furl_file=furl_file)
196 d.addCallback(register, fc_controller, furl_file=furl_file)
187
197
188 reactor.callWhenRunning(set_location_and_register)
198 reactor.callWhenRunning(set_location_and_register)
189 return engine_tub
199 return engine_tub
190
200
191 def start_controller():
201 def start_controller():
192 """
202 """
193 Start the controller by creating the service hierarchy and starting the reactor.
203 Start the controller by creating the service hierarchy and starting the reactor.
194
204
195 This method does the following:
205 This method does the following:
196
206
197 * It starts the controller logging
207 * It starts the controller logging
198 * In execute an import statement for the controller
208 * In execute an import statement for the controller
199 * It creates 2 `foolscap.Tub` instances for the client and the engines
209 * It creates 2 `foolscap.Tub` instances for the client and the engines
200 and registers `foolscap.Referenceables` with the tubs to expose the
210 and registers `foolscap.Referenceables` with the tubs to expose the
201 controller to engines and clients.
211 controller to engines and clients.
202 """
212 """
203 config = kernel_config_manager.get_config_obj()
213 config = kernel_config_manager.get_config_obj()
204
214
205 # Start logging
215 # Start logging
206 logfile = config['controller']['logfile']
216 logfile = config['controller']['logfile']
207 if logfile:
217 if logfile:
208 logfile = logfile + str(os.getpid()) + '.log'
218 logfile = logfile + str(os.getpid()) + '.log'
209 try:
219 try:
210 openLogFile = open(logfile, 'w')
220 openLogFile = open(logfile, 'w')
211 except:
221 except:
212 openLogFile = sys.stdout
222 openLogFile = sys.stdout
213 else:
223 else:
214 openLogFile = sys.stdout
224 openLogFile = sys.stdout
215 log.startLogging(openLogFile)
225 log.startLogging(openLogFile)
216
226
217 # Execute any user defined import statements
227 # Execute any user defined import statements
218 cis = config['controller']['import_statement']
228 cis = config['controller']['import_statement']
219 if cis:
229 if cis:
220 try:
230 try:
221 exec cis in globals(), locals()
231 exec cis in globals(), locals()
222 except:
232 except:
223 log.msg("Error running import_statement: %s" % cis)
233 log.msg("Error running import_statement: %s" % cis)
224
234
225 # Delete old furl files unless the reuse_furls is set
235 # Delete old furl files unless the reuse_furls is set
226 reuse = config['controller']['reuse_furls']
236 reuse = config['controller']['reuse_furls']
227 if not reuse:
237 if not reuse:
228 paths = (config['controller']['engine_furl_file'],
238 paths = (config['controller']['engine_furl_file'],
229 config['controller']['controller_interfaces']['task']['furl_file'],
239 config['controller']['controller_interfaces']['task']['furl_file'],
230 config['controller']['controller_interfaces']['multiengine']['furl_file']
240 config['controller']['controller_interfaces']['multiengine']['furl_file']
231 )
241 )
232 for p in paths:
242 for p in paths:
233 if os.path.isfile(p):
243 if os.path.isfile(p):
234 os.remove(p)
244 os.remove(p)
235
245
236 # Create the service hierarchy
246 # Create the service hierarchy
237 main_service = service.MultiService()
247 main_service = service.MultiService()
238 # The controller service
248 # The controller service
239 controller_service = controllerservice.ControllerService()
249 controller_service = controllerservice.ControllerService()
240 controller_service.setServiceParent(main_service)
250 controller_service.setServiceParent(main_service)
241 # The client tub and all its refereceables
251 # The client tub and all its refereceables
242 client_service = make_client_service(controller_service, config)
252 client_service = make_client_service(controller_service, config)
243 client_service.setServiceParent(main_service)
253 client_service.setServiceParent(main_service)
244 # The engine tub
254 # The engine tub
245 engine_service = make_engine_service(controller_service, config)
255 engine_service = make_engine_service(controller_service, config)
246 engine_service.setServiceParent(main_service)
256 engine_service.setServiceParent(main_service)
247 # Start the controller service and set things running
257 # Start the controller service and set things running
248 main_service.startService()
258 main_service.startService()
249 reactor.run()
259 reactor.run()
250
260
251 def init_config():
261 def init_config():
252 """
262 """
253 Initialize the configuration using default and command line options.
263 Initialize the configuration using default and command line options.
254 """
264 """
255
265
256 parser = OptionParser()
266 parser = OptionParser()
257
267
258 # Client related options
268 # Client related options
259 parser.add_option(
269 parser.add_option(
260 "--client-ip",
270 "--client-ip",
261 type="string",
271 type="string",
262 dest="client_ip",
272 dest="client_ip",
263 help="the IP address or hostname the controller will listen on for client connections"
273 help="the IP address or hostname the controller will listen on for client connections"
264 )
274 )
265 parser.add_option(
275 parser.add_option(
266 "--client-port",
276 "--client-port",
267 type="int",
277 type="int",
268 dest="client_port",
278 dest="client_port",
269 help="the port the controller will listen on for client connections"
279 help="the port the controller will listen on for client connections"
270 )
280 )
271 parser.add_option(
281 parser.add_option(
272 '--client-location',
282 '--client-location',
273 type="string",
283 type="string",
274 dest="client_location",
284 dest="client_location",
275 help="hostname or ip for clients to connect to"
285 help="hostname or ip for clients to connect to"
276 )
286 )
277 parser.add_option(
287 parser.add_option(
278 "-x",
288 "-x",
279 action="store_false",
289 action="store_false",
280 dest="client_secure",
290 dest="client_secure",
281 help="turn off all client security"
291 help="turn off all client security"
282 )
292 )
283 parser.add_option(
293 parser.add_option(
284 '--client-cert-file',
294 '--client-cert-file',
285 type="string",
295 type="string",
286 dest="client_cert_file",
296 dest="client_cert_file",
287 help="file to store the client SSL certificate"
297 help="file to store the client SSL certificate"
288 )
298 )
289 parser.add_option(
299 parser.add_option(
290 '--task-furl-file',
300 '--task-furl-file',
291 type="string",
301 type="string",
292 dest="task_furl_file",
302 dest="task_furl_file",
293 help="file to store the FURL for task clients to connect with"
303 help="file to store the FURL for task clients to connect with"
294 )
304 )
295 parser.add_option(
305 parser.add_option(
296 '--multiengine-furl-file',
306 '--multiengine-furl-file',
297 type="string",
307 type="string",
298 dest="multiengine_furl_file",
308 dest="multiengine_furl_file",
299 help="file to store the FURL for multiengine clients to connect with"
309 help="file to store the FURL for multiengine clients to connect with"
300 )
310 )
301 # Engine related options
311 # Engine related options
302 parser.add_option(
312 parser.add_option(
303 "--engine-ip",
313 "--engine-ip",
304 type="string",
314 type="string",
305 dest="engine_ip",
315 dest="engine_ip",
306 help="the IP address or hostname the controller will listen on for engine connections"
316 help="the IP address or hostname the controller will listen on for engine connections"
307 )
317 )
308 parser.add_option(
318 parser.add_option(
309 "--engine-port",
319 "--engine-port",
310 type="int",
320 type="int",
311 dest="engine_port",
321 dest="engine_port",
312 help="the port the controller will listen on for engine connections"
322 help="the port the controller will listen on for engine connections"
313 )
323 )
314 parser.add_option(
324 parser.add_option(
315 '--engine-location',
325 '--engine-location',
316 type="string",
326 type="string",
317 dest="engine_location",
327 dest="engine_location",
318 help="hostname or ip for engines to connect to"
328 help="hostname or ip for engines to connect to"
319 )
329 )
320 parser.add_option(
330 parser.add_option(
321 "-y",
331 "-y",
322 action="store_false",
332 action="store_false",
323 dest="engine_secure",
333 dest="engine_secure",
324 help="turn off all engine security"
334 help="turn off all engine security"
325 )
335 )
326 parser.add_option(
336 parser.add_option(
327 '--engine-cert-file',
337 '--engine-cert-file',
328 type="string",
338 type="string",
329 dest="engine_cert_file",
339 dest="engine_cert_file",
330 help="file to store the engine SSL certificate"
340 help="file to store the engine SSL certificate"
331 )
341 )
332 parser.add_option(
342 parser.add_option(
333 '--engine-furl-file',
343 '--engine-furl-file',
334 type="string",
344 type="string",
335 dest="engine_furl_file",
345 dest="engine_furl_file",
336 help="file to store the FURL for engines to connect with"
346 help="file to store the FURL for engines to connect with"
337 )
347 )
338 parser.add_option(
348 parser.add_option(
339 "-l", "--logfile",
349 "-l", "--logfile",
340 type="string",
350 type="string",
341 dest="logfile",
351 dest="logfile",
342 help="log file name (default is stdout)"
352 help="log file name (default is stdout)"
343 )
353 )
344 parser.add_option(
354 parser.add_option(
345 "--ipythondir",
346 type="string",
347 dest="ipythondir",
348 help="look for config files and profiles in this directory"
349 )
350 parser.add_option(
351 "-r",
355 "-r",
352 action="store_true",
356 action="store_true",
353 dest="reuse_furls",
357 dest="reuse_furls",
354 help="try to reuse all furl files"
358 help="try to reuse all furl files"
355 )
359 )
356
360
357 (options, args) = parser.parse_args()
361 (options, args) = parser.parse_args()
358
362
359 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
360 config = kernel_config_manager.get_config_obj()
363 config = kernel_config_manager.get_config_obj()
361
364
362 # Update with command line options
365 # Update with command line options
363 if options.client_ip is not None:
366 if options.client_ip is not None:
364 config['controller']['client_tub']['ip'] = options.client_ip
367 config['controller']['client_tub']['ip'] = options.client_ip
365 if options.client_port is not None:
368 if options.client_port is not None:
366 config['controller']['client_tub']['port'] = options.client_port
369 config['controller']['client_tub']['port'] = options.client_port
367 if options.client_location is not None:
370 if options.client_location is not None:
368 config['controller']['client_tub']['location'] = options.client_location
371 config['controller']['client_tub']['location'] = options.client_location
369 if options.client_secure is not None:
372 if options.client_secure is not None:
370 config['controller']['client_tub']['secure'] = options.client_secure
373 config['controller']['client_tub']['secure'] = options.client_secure
371 if options.client_cert_file is not None:
374 if options.client_cert_file is not None:
372 config['controller']['client_tub']['cert_file'] = options.client_cert_file
375 config['controller']['client_tub']['cert_file'] = options.client_cert_file
373 if options.task_furl_file is not None:
376 if options.task_furl_file is not None:
374 config['controller']['controller_interfaces']['task']['furl_file'] = options.task_furl_file
377 config['controller']['controller_interfaces']['task']['furl_file'] = options.task_furl_file
375 if options.multiengine_furl_file is not None:
378 if options.multiengine_furl_file is not None:
376 config['controller']['controller_interfaces']['multiengine']['furl_file'] = options.multiengine_furl_file
379 config['controller']['controller_interfaces']['multiengine']['furl_file'] = options.multiengine_furl_file
377 if options.engine_ip is not None:
380 if options.engine_ip is not None:
378 config['controller']['engine_tub']['ip'] = options.engine_ip
381 config['controller']['engine_tub']['ip'] = options.engine_ip
379 if options.engine_port is not None:
382 if options.engine_port is not None:
380 config['controller']['engine_tub']['port'] = options.engine_port
383 config['controller']['engine_tub']['port'] = options.engine_port
381 if options.engine_location is not None:
384 if options.engine_location is not None:
382 config['controller']['engine_tub']['location'] = options.engine_location
385 config['controller']['engine_tub']['location'] = options.engine_location
383 if options.engine_secure is not None:
386 if options.engine_secure is not None:
384 config['controller']['engine_tub']['secure'] = options.engine_secure
387 config['controller']['engine_tub']['secure'] = options.engine_secure
385 if options.engine_cert_file is not None:
388 if options.engine_cert_file is not None:
386 config['controller']['engine_tub']['cert_file'] = options.engine_cert_file
389 config['controller']['engine_tub']['cert_file'] = options.engine_cert_file
387 if options.engine_furl_file is not None:
390 if options.engine_furl_file is not None:
388 config['controller']['engine_furl_file'] = options.engine_furl_file
391 config['controller']['engine_furl_file'] = options.engine_furl_file
389 if options.reuse_furls is not None:
392 if options.reuse_furls is not None:
390 config['controller']['reuse_furls'] = options.reuse_furls
393 config['controller']['reuse_furls'] = options.reuse_furls
391
394
392 if options.logfile is not None:
395 if options.logfile is not None:
393 config['controller']['logfile'] = options.logfile
396 config['controller']['logfile'] = options.logfile
394
397
395 kernel_config_manager.update_config_obj(config)
398 kernel_config_manager.update_config_obj(config)
396
399
397 def main():
400 def main():
398 """
401 """
399 After creating the configuration information, start the controller.
402 After creating the configuration information, start the controller.
400 """
403 """
401 init_config()
404 init_config()
402 start_controller()
405 start_controller()
403
406
404 if __name__ == "__main__":
407 if __name__ == "__main__":
405 main()
408 main()
@@ -1,182 +1,186 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 """Start the IPython Engine."""
4 """Start the IPython Engine."""
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 # Python looks for an empty string at the beginning of sys.path to enable
19 # Python looks for an empty string at the beginning of sys.path to enable
20 # importing from the cwd.
20 # importing from the cwd.
21 import sys
21 import sys
22 sys.path.insert(0, '')
22 sys.path.insert(0, '')
23
23
24 import sys, os
24 import sys, os
25 from optparse import OptionParser
25 from optparse import OptionParser
26
26
27 from twisted.application import service
27 from twisted.application import service
28 from twisted.internet import reactor
28 from twisted.internet import reactor
29 from twisted.python import log
29 from twisted.python import log
30
30
31 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
31 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
32
32
33 from IPython.kernel.core.config import config_manager as core_config_manager
33 from IPython.kernel.core.config import config_manager as core_config_manager
34 from IPython.config.cutils import import_item
34 from IPython.config.cutils import import_item
35 from IPython.kernel.engineservice import EngineService
35 from IPython.kernel.engineservice import EngineService
36
37 # Create various ipython directories if they don't exist.
38 # This must be done before IPython.kernel.config is imported.
39 from IPython.iplib import user_setup
40 from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir
41 if os.name == 'posix':
42 rc_suffix = ''
43 else:
44 rc_suffix = '.ini'
45 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
46 get_log_dir()
47 get_security_dir()
48
36 from IPython.kernel.config import config_manager as kernel_config_manager
49 from IPython.kernel.config import config_manager as kernel_config_manager
37 from IPython.kernel.engineconnector import EngineConnector
50 from IPython.kernel.engineconnector import EngineConnector
38
51
39
52
40 #-------------------------------------------------------------------------------
53 #-------------------------------------------------------------------------------
41 # Code
54 # Code
42 #-------------------------------------------------------------------------------
55 #-------------------------------------------------------------------------------
43
56
44 def start_engine():
57 def start_engine():
45 """
58 """
46 Start the engine, by creating it and starting the Twisted reactor.
59 Start the engine, by creating it and starting the Twisted reactor.
47
60
48 This method does:
61 This method does:
49
62
50 * If it exists, runs the `mpi_import_statement` to call `MPI_Init`
63 * If it exists, runs the `mpi_import_statement` to call `MPI_Init`
51 * Starts the engine logging
64 * Starts the engine logging
52 * Creates an IPython shell and wraps it in an `EngineService`
65 * Creates an IPython shell and wraps it in an `EngineService`
53 * Creates a `foolscap.Tub` to use in connecting to a controller.
66 * Creates a `foolscap.Tub` to use in connecting to a controller.
54 * Uses the tub and the `EngineService` along with a Foolscap URL
67 * Uses the tub and the `EngineService` along with a Foolscap URL
55 (or FURL) to connect to the controller and register the engine
68 (or FURL) to connect to the controller and register the engine
56 with the controller
69 with the controller
57 """
70 """
58 kernel_config = kernel_config_manager.get_config_obj()
71 kernel_config = kernel_config_manager.get_config_obj()
59 core_config = core_config_manager.get_config_obj()
72 core_config = core_config_manager.get_config_obj()
60
73
61
74
62 # Execute the mpi import statement that needs to call MPI_Init
75 # Execute the mpi import statement that needs to call MPI_Init
63 global mpi
76 global mpi
64 mpikey = kernel_config['mpi']['default']
77 mpikey = kernel_config['mpi']['default']
65 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
78 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
66 if mpi_import_statement is not None:
79 if mpi_import_statement is not None:
67 try:
80 try:
68 exec mpi_import_statement in globals()
81 exec mpi_import_statement in globals()
69 except:
82 except:
70 mpi = None
83 mpi = None
71 else:
84 else:
72 mpi = None
85 mpi = None
73
86
74 # Start logging
87 # Start logging
75 logfile = kernel_config['engine']['logfile']
88 logfile = kernel_config['engine']['logfile']
76 if logfile:
89 if logfile:
77 logfile = logfile + str(os.getpid()) + '.log'
90 logfile = logfile + str(os.getpid()) + '.log'
78 try:
91 try:
79 openLogFile = open(logfile, 'w')
92 openLogFile = open(logfile, 'w')
80 except:
93 except:
81 openLogFile = sys.stdout
94 openLogFile = sys.stdout
82 else:
95 else:
83 openLogFile = sys.stdout
96 openLogFile = sys.stdout
84 log.startLogging(openLogFile)
97 log.startLogging(openLogFile)
85
98
86 # Create the underlying shell class and EngineService
99 # Create the underlying shell class and EngineService
87 shell_class = import_item(core_config['shell']['shell_class'])
100 shell_class = import_item(core_config['shell']['shell_class'])
88 engine_service = EngineService(shell_class, mpi=mpi)
101 engine_service = EngineService(shell_class, mpi=mpi)
89 shell_import_statement = core_config['shell']['import_statement']
102 shell_import_statement = core_config['shell']['import_statement']
90 if shell_import_statement:
103 if shell_import_statement:
91 try:
104 try:
92 engine_service.execute(shell_import_statement)
105 engine_service.execute(shell_import_statement)
93 except:
106 except:
94 log.msg("Error running import_statement: %s" % shell_import_statement)
107 log.msg("Error running import_statement: %s" % shell_import_statement)
95
108
96 # Create the service hierarchy
109 # Create the service hierarchy
97 main_service = service.MultiService()
110 main_service = service.MultiService()
98 engine_service.setServiceParent(main_service)
111 engine_service.setServiceParent(main_service)
99 tub_service = Tub()
112 tub_service = Tub()
100 tub_service.setServiceParent(main_service)
113 tub_service.setServiceParent(main_service)
101 # This needs to be called before the connection is initiated
114 # This needs to be called before the connection is initiated
102 main_service.startService()
115 main_service.startService()
103
116
104 # This initiates the connection to the controller and calls
117 # This initiates the connection to the controller and calls
105 # register_engine to tell the controller we are ready to do work
118 # register_engine to tell the controller we are ready to do work
106 engine_connector = EngineConnector(tub_service)
119 engine_connector = EngineConnector(tub_service)
107 furl_file = kernel_config['engine']['furl_file']
120 furl_file = kernel_config['engine']['furl_file']
108 log.msg("Using furl file: %s" % furl_file)
121 log.msg("Using furl file: %s" % furl_file)
109
122
110 def call_connect(engine_service, furl_file):
123 def call_connect(engine_service, furl_file):
111 d = engine_connector.connect_to_controller(engine_service, furl_file)
124 d = engine_connector.connect_to_controller(engine_service, furl_file)
112 def handle_error(f):
125 def handle_error(f):
113 # If this print statement is replaced by a log.err(f) I get
126 # If this print statement is replaced by a log.err(f) I get
114 # an unhandled error, which makes no sense. I shouldn't have
127 # an unhandled error, which makes no sense. I shouldn't have
115 # to use a print statement here. My only thought is that
128 # to use a print statement here. My only thought is that
116 # at the beginning of the process the logging is still starting up
129 # at the beginning of the process the logging is still starting up
117 print "error connecting to controller:", f.getErrorMessage()
130 print "error connecting to controller:", f.getErrorMessage()
118 reactor.callLater(0.1, reactor.stop)
131 reactor.callLater(0.1, reactor.stop)
119 d.addErrback(handle_error)
132 d.addErrback(handle_error)
120
133
121 reactor.callWhenRunning(call_connect, engine_service, furl_file)
134 reactor.callWhenRunning(call_connect, engine_service, furl_file)
122 reactor.run()
135 reactor.run()
123
136
124
137
125 def init_config():
138 def init_config():
126 """
139 """
127 Initialize the configuration using default and command line options.
140 Initialize the configuration using default and command line options.
128 """
141 """
129
142
130 parser = OptionParser()
143 parser = OptionParser()
131
144
132 parser.add_option(
145 parser.add_option(
133 "--furl-file",
146 "--furl-file",
134 type="string",
147 type="string",
135 dest="furl_file",
148 dest="furl_file",
136 help="The filename containing the FURL of the controller"
149 help="The filename containing the FURL of the controller"
137 )
150 )
138 parser.add_option(
151 parser.add_option(
139 "--mpi",
152 "--mpi",
140 type="string",
153 type="string",
141 dest="mpi",
154 dest="mpi",
142 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
155 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
143 )
156 )
144 parser.add_option(
157 parser.add_option(
145 "-l",
158 "-l",
146 "--logfile",
159 "--logfile",
147 type="string",
160 type="string",
148 dest="logfile",
161 dest="logfile",
149 help="log file name (default is stdout)"
162 help="log file name (default is stdout)"
150 )
163 )
151 parser.add_option(
152 "--ipythondir",
153 type="string",
154 dest="ipythondir",
155 help="look for config files and profiles in this directory"
156 )
157
164
158 (options, args) = parser.parse_args()
165 (options, args) = parser.parse_args()
159
166
160 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
161 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
162
163 kernel_config = kernel_config_manager.get_config_obj()
167 kernel_config = kernel_config_manager.get_config_obj()
164 # Now override with command line options
168 # Now override with command line options
165 if options.furl_file is not None:
169 if options.furl_file is not None:
166 kernel_config['engine']['furl_file'] = options.furl_file
170 kernel_config['engine']['furl_file'] = options.furl_file
167 if options.logfile is not None:
171 if options.logfile is not None:
168 kernel_config['engine']['logfile'] = options.logfile
172 kernel_config['engine']['logfile'] = options.logfile
169 if options.mpi is not None:
173 if options.mpi is not None:
170 kernel_config['mpi']['default'] = options.mpi
174 kernel_config['mpi']['default'] = options.mpi
171
175
172
176
173 def main():
177 def main():
174 """
178 """
175 After creating the configuration information, start the engine.
179 After creating the configuration information, start the engine.
176 """
180 """
177 init_config()
181 init_config()
178 start_engine()
182 start_engine()
179
183
180
184
181 if __name__ == "__main__":
185 if __name__ == "__main__":
182 main()
186 main()
@@ -1,291 +1,297 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Tests for genutils.py"""
3 """Tests for genutils.py"""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # stdlib
18 # stdlib
19 import os
19 import os
20 import shutil
20 import shutil
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23
23
24 from os.path import join, abspath, split
24 from os.path import join, abspath, split
25
25
26 # third-party
26 # third-party
27 import nose.tools as nt
27 import nose.tools as nt
28
28
29 from nose import with_setup
29 from nose import with_setup
30 from nose.tools import raises
30 from nose.tools import raises
31
31
32 # Our own
32 # Our own
33 import IPython
33 import IPython
34 from IPython import genutils
34 from IPython import genutils
35 from IPython.testing.decorators import skipif, skip_if_not_win32
35 from IPython.testing.decorators import skipif, skip_if_not_win32
36
36
37 # Platform-dependent imports
37 # Platform-dependent imports
38 try:
38 try:
39 import _winreg as wreg
39 import _winreg as wreg
40 except ImportError:
40 except ImportError:
41 #Fake _winreg module on none windows platforms
41 #Fake _winreg module on none windows platforms
42 import new
42 import new
43 sys.modules["_winreg"] = new.module("_winreg")
43 sys.modules["_winreg"] = new.module("_winreg")
44 import _winreg as wreg
44 import _winreg as wreg
45 #Add entries that needs to be stubbed by the testing code
45 #Add entries that needs to be stubbed by the testing code
46 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
46 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Globals
49 # Globals
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 env = os.environ
51 env = os.environ
52 TEST_FILE_PATH = split(abspath(__file__))[0]
52 TEST_FILE_PATH = split(abspath(__file__))[0]
53 TMP_TEST_DIR = tempfile.mkdtemp()
53 TMP_TEST_DIR = tempfile.mkdtemp()
54 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
54 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
55 IP_TEST_DIR = join(HOME_TEST_DIR,'_ipython')
55 IP_TEST_DIR = join(HOME_TEST_DIR,'_ipython')
56 #
56 #
57 # Setup/teardown functions/decorators
57 # Setup/teardown functions/decorators
58 #
58 #
59
59
60 def setup():
60 def setup():
61 """Setup testenvironment for the module:
61 """Setup testenvironment for the module:
62
62
63 - Adds dummy home dir tree
63 - Adds dummy home dir tree
64 """
64 """
65 # Do not mask exceptions here. In particular, catching WindowsError is a
65 # Do not mask exceptions here. In particular, catching WindowsError is a
66 # problem because that exception is only defined on Windows...
66 # problem because that exception is only defined on Windows...
67 os.makedirs(IP_TEST_DIR)
67 os.makedirs(IP_TEST_DIR)
68
68
69 def teardown():
69 def teardown():
70 """Teardown testenvironment for the module:
70 """Teardown testenvironment for the module:
71
71
72 - Remove dummy home dir tree
72 - Remove dummy home dir tree
73 """
73 """
74 # Note: we remove the parent test dir, which is the root of all test
74 # Note: we remove the parent test dir, which is the root of all test
75 # subdirs we may have created. Use shutil instead of os.removedirs, so
75 # subdirs we may have created. Use shutil instead of os.removedirs, so
76 # that non-empty directories are all recursively removed.
76 # that non-empty directories are all recursively removed.
77 shutil.rmtree(TMP_TEST_DIR)
77 shutil.rmtree(TMP_TEST_DIR)
78
78
79
79
80 def setup_environment():
80 def setup_environment():
81 """Setup testenvironment for some functions that are tested
81 """Setup testenvironment for some functions that are tested
82 in this module. In particular this functions stores attributes
82 in this module. In particular this functions stores attributes
83 and other things that we need to stub in some test functions.
83 and other things that we need to stub in some test functions.
84 This needs to be done on a function level and not module level because
84 This needs to be done on a function level and not module level because
85 each testfunction needs a pristine environment.
85 each testfunction needs a pristine environment.
86 """
86 """
87 global oldstuff, platformstuff
87 global oldstuff, platformstuff
88 oldstuff = (env.copy(), os.name, genutils.get_home_dir, IPython.__file__,)
88 oldstuff = (env.copy(), os.name, genutils.get_home_dir, IPython.__file__,)
89
89
90 if os.name == 'nt':
90 if os.name == 'nt':
91 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
91 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
92
92
93 if 'IPYTHONDIR' in env:
93 if 'IPYTHONDIR' in env:
94 del env['IPYTHONDIR']
94 del env['IPYTHONDIR']
95
95
96 def teardown_environment():
96 def teardown_environment():
97 """Restore things that were remebered by the setup_environment function
97 """Restore things that were remebered by the setup_environment function
98 """
98 """
99 (oldenv, os.name, genutils.get_home_dir, IPython.__file__,) = oldstuff
99 (oldenv, os.name, genutils.get_home_dir, IPython.__file__,) = oldstuff
100 for key in env.keys():
100 for key in env.keys():
101 if key not in oldenv:
101 if key not in oldenv:
102 del env[key]
102 del env[key]
103 env.update(oldenv)
103 env.update(oldenv)
104 if hasattr(sys, 'frozen'):
104 if hasattr(sys, 'frozen'):
105 del sys.frozen
105 del sys.frozen
106 if os.name == 'nt':
106 if os.name == 'nt':
107 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
107 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
108
108
109 # Build decorator that uses the setup_environment/setup_environment
109 # Build decorator that uses the setup_environment/setup_environment
110 with_enivronment = with_setup(setup_environment, teardown_environment)
110 with_enivronment = with_setup(setup_environment, teardown_environment)
111
111
112
112
113 #
113 #
114 # Tests for get_home_dir
114 # Tests for get_home_dir
115 #
115 #
116
116
117 @skip_if_not_win32
117 @skip_if_not_win32
118 @with_enivronment
118 @with_enivronment
119 def test_get_home_dir_1():
119 def test_get_home_dir_1():
120 """Testcase for py2exe logic, un-compressed lib
120 """Testcase for py2exe logic, un-compressed lib
121 """
121 """
122 sys.frozen = True
122 sys.frozen = True
123
123
124 #fake filename for IPython.__init__
124 #fake filename for IPython.__init__
125 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
125 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
126
126
127 home_dir = genutils.get_home_dir()
127 home_dir = genutils.get_home_dir()
128 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
128 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
129
129
130 @skip_if_not_win32
130 @skip_if_not_win32
131 @with_enivronment
131 @with_enivronment
132 def test_get_home_dir_2():
132 def test_get_home_dir_2():
133 """Testcase for py2exe logic, compressed lib
133 """Testcase for py2exe logic, compressed lib
134 """
134 """
135 sys.frozen = True
135 sys.frozen = True
136 #fake filename for IPython.__init__
136 #fake filename for IPython.__init__
137 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
137 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
138
138
139 home_dir = genutils.get_home_dir()
139 home_dir = genutils.get_home_dir()
140 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR).lower())
140 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR).lower())
141
141
142 @with_enivronment
142 @with_enivronment
143 def test_get_home_dir_3():
143 def test_get_home_dir_3():
144 """Testcase $HOME is set, then use its value as home directory."""
144 """Testcase $HOME is set, then use its value as home directory."""
145 env["HOME"] = HOME_TEST_DIR
145 env["HOME"] = HOME_TEST_DIR
146 home_dir = genutils.get_home_dir()
146 home_dir = genutils.get_home_dir()
147 nt.assert_equal(home_dir, env["HOME"])
147 nt.assert_equal(home_dir, env["HOME"])
148
148
149 @with_enivronment
149 @with_enivronment
150 def test_get_home_dir_4():
150 def test_get_home_dir_4():
151 """Testcase $HOME is not set, os=='poix'.
151 """Testcase $HOME is not set, os=='poix'.
152 This should fail with HomeDirError"""
152 This should fail with HomeDirError"""
153
153
154 os.name = 'posix'
154 os.name = 'posix'
155 if 'HOME' in env: del env['HOME']
155 if 'HOME' in env: del env['HOME']
156 nt.assert_raises(genutils.HomeDirError, genutils.get_home_dir)
156 nt.assert_raises(genutils.HomeDirError, genutils.get_home_dir)
157
157
158 @skip_if_not_win32
158 @skip_if_not_win32
159 @with_enivronment
159 @with_enivronment
160 def test_get_home_dir_5():
160 def test_get_home_dir_5():
161 """Testcase $HOME is not set, os=='nt'
161 """Testcase $HOME is not set, os=='nt'
162 env['HOMEDRIVE'],env['HOMEPATH'] points to path."""
162 env['HOMEDRIVE'],env['HOMEPATH'] points to path."""
163
163
164 os.name = 'nt'
164 os.name = 'nt'
165 if 'HOME' in env: del env['HOME']
165 if 'HOME' in env: del env['HOME']
166 env['HOMEDRIVE'], env['HOMEPATH'] = os.path.splitdrive(HOME_TEST_DIR)
166 env['HOMEDRIVE'], env['HOMEPATH'] = os.path.splitdrive(HOME_TEST_DIR)
167
167
168 home_dir = genutils.get_home_dir()
168 home_dir = genutils.get_home_dir()
169 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
169 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
170
170
171 @skip_if_not_win32
171 @skip_if_not_win32
172 @with_enivronment
172 @with_enivronment
173 def test_get_home_dir_6():
173 def test_get_home_dir_6():
174 """Testcase $HOME is not set, os=='nt'
174 """Testcase $HOME is not set, os=='nt'
175 env['HOMEDRIVE'],env['HOMEPATH'] do not point to path.
175 env['HOMEDRIVE'],env['HOMEPATH'] do not point to path.
176 env['USERPROFILE'] points to path
176 env['USERPROFILE'] points to path
177 """
177 """
178
178
179 os.name = 'nt'
179 os.name = 'nt'
180 if 'HOME' in env: del env['HOME']
180 if 'HOME' in env: del env['HOME']
181 env['HOMEDRIVE'], env['HOMEPATH'] = os.path.abspath(TEST_FILE_PATH), "DOES NOT EXIST"
181 env['HOMEDRIVE'], env['HOMEPATH'] = os.path.abspath(TEST_FILE_PATH), "DOES NOT EXIST"
182 env["USERPROFILE"] = abspath(HOME_TEST_DIR)
182 env["USERPROFILE"] = abspath(HOME_TEST_DIR)
183
183
184 home_dir = genutils.get_home_dir()
184 home_dir = genutils.get_home_dir()
185 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
185 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
186
186
187 # Should we stub wreg fully so we can run the test on all platforms?
187 # Should we stub wreg fully so we can run the test on all platforms?
188 @skip_if_not_win32
188 @skip_if_not_win32
189 @with_enivronment
189 @with_enivronment
190 def test_get_home_dir_7():
190 def test_get_home_dir_7():
191 """Testcase $HOME is not set, os=='nt'
191 """Testcase $HOME is not set, os=='nt'
192 env['HOMEDRIVE'],env['HOMEPATH'], env['USERPROFILE'] missing
192 env['HOMEDRIVE'],env['HOMEPATH'], env['USERPROFILE'] missing
193 """
193 """
194 os.name = 'nt'
194 os.name = 'nt'
195 if 'HOME' in env: del env['HOME']
195 if 'HOME' in env: del env['HOME']
196 if 'HOMEDRIVE' in env: del env['HOMEDRIVE']
196 if 'HOMEDRIVE' in env: del env['HOMEDRIVE']
197
197
198 #Stub windows registry functions
198 #Stub windows registry functions
199 def OpenKey(x, y):
199 def OpenKey(x, y):
200 class key:
200 class key:
201 def Close(self):
201 def Close(self):
202 pass
202 pass
203 return key()
203 return key()
204 def QueryValueEx(x, y):
204 def QueryValueEx(x, y):
205 return [abspath(HOME_TEST_DIR)]
205 return [abspath(HOME_TEST_DIR)]
206
206
207 wreg.OpenKey = OpenKey
207 wreg.OpenKey = OpenKey
208 wreg.QueryValueEx = QueryValueEx
208 wreg.QueryValueEx = QueryValueEx
209
209
210 home_dir = genutils.get_home_dir()
210 home_dir = genutils.get_home_dir()
211 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
211 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
212
212
213
214 #
213 #
215 # Tests for get_ipython_dir
214 # Tests for get_ipython_dir
216 #
215 #
217
216
218 @with_enivronment
217 @with_enivronment
219 def test_get_ipython_dir_1():
218 def test_get_ipython_dir_1():
220 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
219 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
221 env['IPYTHONDIR'] = "someplace/.ipython"
220 env['IPYTHONDIR'] = "someplace/.ipython"
222 ipdir = genutils.get_ipython_dir()
221 ipdir = genutils.get_ipython_dir()
223 nt.assert_equal(ipdir, os.path.abspath("someplace/.ipython"))
222 nt.assert_equal(ipdir, os.path.abspath("someplace/.ipython"))
224
223
225
224
226 @with_enivronment
225 @with_enivronment
227 def test_get_ipython_dir_2():
226 def test_get_ipython_dir_2():
228 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
227 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
229 genutils.get_home_dir = lambda : "someplace"
228 genutils.get_home_dir = lambda : "someplace"
230 os.name = "posix"
229 os.name = "posix"
231 ipdir = genutils.get_ipython_dir()
230 ipdir = genutils.get_ipython_dir()
232 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", ".ipython")))
231 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", ".ipython")))
233
232
234 @with_enivronment
233 @with_enivronment
235 def test_get_ipython_dir_3():
234 def test_get_ipython_dir_3():
236 """test_get_ipython_dir_3, Testcase to see if we can call get_ipython_dir without Exceptions."""
235 """test_get_ipython_dir_3, Testcase to see if we can call get_ipython_dir without Exceptions."""
237 genutils.get_home_dir = lambda : "someplace"
236 genutils.get_home_dir = lambda : "someplace"
238 os.name = "nt"
237 os.name = "nt"
239 ipdir = genutils.get_ipython_dir()
238 ipdir = genutils.get_ipython_dir()
240 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython")))
239 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython")))
241
240
242
243 #
241 #
244 # Tests for get_security_dir
242 # Tests for get_security_dir
245 #
243 #
246
244
247 @with_enivronment
245 @with_enivronment
248 def test_get_security_dir():
246 def test_get_security_dir():
249 """Testcase to see if we can call get_security_dir without Exceptions."""
247 """Testcase to see if we can call get_security_dir without Exceptions."""
250 sdir = genutils.get_security_dir()
248 sdir = genutils.get_security_dir()
251
249
250 #
251 # Tests for get_log_dir
252 #
253
254 @with_enivronment
255 def test_get_log_dir():
256 """Testcase to see if we can call get_log_dir without Exceptions."""
257 sdir = genutils.get_log_dir()
252
258
253 #
259 #
254 # Tests for popkey
260 # Tests for popkey
255 #
261 #
256
262
257 def test_popkey_1():
263 def test_popkey_1():
258 """test_popkey_1, Basic usage test of popkey
264 """test_popkey_1, Basic usage test of popkey
259 """
265 """
260 dct = dict(a=1, b=2, c=3)
266 dct = dict(a=1, b=2, c=3)
261 nt.assert_equal(genutils.popkey(dct, "a"), 1)
267 nt.assert_equal(genutils.popkey(dct, "a"), 1)
262 nt.assert_equal(dct, dict(b=2, c=3))
268 nt.assert_equal(dct, dict(b=2, c=3))
263 nt.assert_equal(genutils.popkey(dct, "b"), 2)
269 nt.assert_equal(genutils.popkey(dct, "b"), 2)
264 nt.assert_equal(dct, dict(c=3))
270 nt.assert_equal(dct, dict(c=3))
265 nt.assert_equal(genutils.popkey(dct, "c"), 3)
271 nt.assert_equal(genutils.popkey(dct, "c"), 3)
266 nt.assert_equal(dct, dict())
272 nt.assert_equal(dct, dict())
267
273
268 def test_popkey_2():
274 def test_popkey_2():
269 """test_popkey_2, Test to see that popkey of non occuring keys
275 """test_popkey_2, Test to see that popkey of non occuring keys
270 generates a KeyError exception
276 generates a KeyError exception
271 """
277 """
272 dct = dict(a=1, b=2, c=3)
278 dct = dict(a=1, b=2, c=3)
273 nt.assert_raises(KeyError, genutils.popkey, dct, "d")
279 nt.assert_raises(KeyError, genutils.popkey, dct, "d")
274
280
275 def test_popkey_3():
281 def test_popkey_3():
276 """test_popkey_3, Tests to see that popkey calls returns the correct value
282 """test_popkey_3, Tests to see that popkey calls returns the correct value
277 and that the key/value was removed from the dict.
283 and that the key/value was removed from the dict.
278 """
284 """
279 dct = dict(a=1, b=2, c=3)
285 dct = dict(a=1, b=2, c=3)
280 nt.assert_equal(genutils.popkey(dct, "A", 13), 13)
286 nt.assert_equal(genutils.popkey(dct, "A", 13), 13)
281 nt.assert_equal(dct, dict(a=1, b=2, c=3))
287 nt.assert_equal(dct, dict(a=1, b=2, c=3))
282 nt.assert_equal(genutils.popkey(dct, "B", 14), 14)
288 nt.assert_equal(genutils.popkey(dct, "B", 14), 14)
283 nt.assert_equal(dct, dict(a=1, b=2, c=3))
289 nt.assert_equal(dct, dict(a=1, b=2, c=3))
284 nt.assert_equal(genutils.popkey(dct, "C", 15), 15)
290 nt.assert_equal(genutils.popkey(dct, "C", 15), 15)
285 nt.assert_equal(dct, dict(a=1, b=2, c=3))
291 nt.assert_equal(dct, dict(a=1, b=2, c=3))
286 nt.assert_equal(genutils.popkey(dct, "a"), 1)
292 nt.assert_equal(genutils.popkey(dct, "a"), 1)
287 nt.assert_equal(dct, dict(b=2, c=3))
293 nt.assert_equal(dct, dict(b=2, c=3))
288 nt.assert_equal(genutils.popkey(dct, "b"), 2)
294 nt.assert_equal(genutils.popkey(dct, "b"), 2)
289 nt.assert_equal(dct, dict(c=3))
295 nt.assert_equal(dct, dict(c=3))
290 nt.assert_equal(genutils.popkey(dct, "c"), 3)
296 nt.assert_equal(genutils.popkey(dct, "c"), 3)
291 nt.assert_equal(dct, dict())
297 nt.assert_equal(dct, dict())
General Comments 0
You need to be logged in to leave comments. Login now