##// END OF EJS Templates
Merge with upstream
Fernando Perez -
r1670:099aada6 merge
parent child Browse files
Show More
@@ -0,0 +1,26 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the interpreter.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 from IPython.kernel.core.interpreter import Interpreter
19
20 def test_unicode():
21 """ Test unicode handling with the interpreter.
22 """
23 i = Interpreter()
24 i.execute_python(u'print "ù"')
25 i.execute_python('print "ù"')
26
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,32 b''
1 # encoding: utf-8
2
3 """Tests for genutils.py"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 from IPython import genutils
19
20
21 def test_get_home_dir():
22 """Make sure we can get the home directory."""
23 home_dir = genutils.get_home_dir()
24
25 def test_get_ipython_dir():
26 """Make sure we can get the ipython directory."""
27 ipdir = genutils.get_ipython_dir()
28
29 def test_get_security_dir():
30 """Make sure we can get the ipython/security directory."""
31 sdir = genutils.get_security_dir()
32 No newline at end of file
@@ -1,43 +1,45 b''
1 # -*- Mode: Shell-Script -*- Not really, but shows comments correctly
1 # -*- Mode: Shell-Script -*- Not really, but shows comments correctly
2 #***************************************************************************
2 #***************************************************************************
3 #
3 #
4 # Configuration file for ipython -- ipythonrc format
4 # Configuration file for ipython -- ipythonrc format
5 #
5 #
6 # The format of this file is one of 'key value' lines.
6 # The format of this file is one of 'key value' lines.
7 # Lines containing only whitespace at the beginning and then a # are ignored
7 # Lines containing only whitespace at the beginning and then a # are ignored
8 # as comments. But comments can NOT be put on lines with data.
8 # as comments. But comments can NOT be put on lines with data.
9 #***************************************************************************
9 #***************************************************************************
10
10
11 # If this file is found in the user's ~/.ipython directory as
11 # If this file is found in the user's ~/.ipython directory as
12 # ipythonrc-physics, it can be loaded by calling passing the '-profile
12 # ipythonrc-physics, it can be loaded by calling passing the '-profile
13 # physics' (or '-p physics') option to IPython.
13 # physics' (or '-p physics') option to IPython.
14
14
15 # This profile loads modules useful for doing interactive calculations with
15 # This profile loads modules useful for doing interactive calculations with
16 # physical quantities (with units). It relies on modules from Konrad Hinsen's
16 # physical quantities (with units). It relies on modules from Konrad Hinsen's
17 # ScientificPython (http://dirac.cnrs-orleans.fr/ScientificPython/)
17 # ScientificPython (http://dirac.cnrs-orleans.fr/ScientificPython/)
18
18
19 # First load basic user configuration
19 # First load basic user configuration
20 include ipythonrc
20 include ipythonrc
21
21
22 # import ...
22 # import ...
23 # Module with alternate input syntax for PhysicalQuantity objects.
23 # Module with alternate input syntax for PhysicalQuantity objects.
24 import_mod IPython.Extensions.PhysicalQInput
24 import_mod IPython.Extensions.PhysicalQInput
25
25
26 # from ... import *
26 # from ... import *
27 # math CANNOT be imported after PhysicalQInteractive. It will override the
27 # math CANNOT be imported after PhysicalQInteractive. It will override the
28 # functions defined there.
28 # functions defined there.
29 import_all math IPython.Extensions.PhysicalQInteractive
29 import_all math IPython.Extensions.PhysicalQInteractive
30
30
31 # from ... import ...
31 # from ... import ...
32 import_some
32 import_some
33
33
34 # code
34 # code
35 execute q = PhysicalQuantityInteractive
35 execute q = PhysicalQuantityInteractive
36 execute g = PhysicalQuantityInteractive('9.8 m/s**2')
36 execute g = PhysicalQuantityInteractive('9.8 m/s**2')
37 ececute rad = pi/180.
37 ececute rad = pi/180.
38 execute print '*** q is an alias for PhysicalQuantityInteractive'
38 execute print '*** q is an alias for PhysicalQuantityInteractive'
39 execute print '*** g = 9.8 m/s^2 has been defined'
39 execute print '*** g = 9.8 m/s^2 has been defined'
40 execute print '*** rad = pi/180 has been defined'
40 execute print '*** rad = pi/180 has been defined'
41 execute import ipy_constants as C
42 execute print '*** C is the physical constants module'
41
43
42 # Files to execute
44 # Files to execute
43 execfile
45 execfile
@@ -1,104 +1,106 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This is the official entry point to IPython's configuration system. """
3 """This is the official entry point to IPython's configuration system. """
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
18 import os
19 from IPython.config.cutils import get_home_dir, get_ipython_dir
19 from os.path import join as pjoin
20
21 from IPython.genutils import get_home_dir, get_ipython_dir
20 from IPython.external.configobj import ConfigObj
22 from IPython.external.configobj import ConfigObj
21
23
22 # Traitlets config imports
24 # Traitlets config imports
23 from IPython.config import traitlets
25 from IPython.config import traitlets
24 from IPython.config.config import *
26 from IPython.config.config import *
25 from traitlets import *
27 from traitlets import *
26
28
27 class ConfigObjManager(object):
29 class ConfigObjManager(object):
28
30
29 def __init__(self, configObj, filename):
31 def __init__(self, configObj, filename):
30 self.current = configObj
32 self.current = configObj
31 self.current.indent_type = ' '
33 self.current.indent_type = ' '
32 self.filename = filename
34 self.filename = filename
33 # self.write_default_config_file()
35 # self.write_default_config_file()
34
36
35 def get_config_obj(self):
37 def get_config_obj(self):
36 return self.current
38 return self.current
37
39
38 def update_config_obj(self, newConfig):
40 def update_config_obj(self, newConfig):
39 self.current.merge(newConfig)
41 self.current.merge(newConfig)
40
42
41 def update_config_obj_from_file(self, filename):
43 def update_config_obj_from_file(self, filename):
42 newConfig = ConfigObj(filename, file_error=False)
44 newConfig = ConfigObj(filename, file_error=False)
43 self.current.merge(newConfig)
45 self.current.merge(newConfig)
44
46
45 def update_config_obj_from_default_file(self, ipythondir=None):
47 def update_config_obj_from_default_file(self, ipythondir=None):
46 fname = self.resolve_file_path(self.filename, ipythondir)
48 fname = self.resolve_file_path(self.filename, ipythondir)
47 self.update_config_obj_from_file(fname)
49 self.update_config_obj_from_file(fname)
48
50
49 def write_config_obj_to_file(self, filename):
51 def write_config_obj_to_file(self, filename):
50 f = open(filename, 'w')
52 f = open(filename, 'w')
51 self.current.write(f)
53 self.current.write(f)
52 f.close()
54 f.close()
53
55
54 def write_default_config_file(self):
56 def write_default_config_file(self):
55 ipdir = get_ipython_dir()
57 ipdir = get_ipython_dir()
56 fname = ipdir + '/' + self.filename
58 fname = pjoin(ipdir, self.filename)
57 if not os.path.isfile(fname):
59 if not os.path.isfile(fname):
58 print "Writing the configuration file to: " + fname
60 print "Writing the configuration file to: " + fname
59 self.write_config_obj_to_file(fname)
61 self.write_config_obj_to_file(fname)
60
62
61 def _import(self, key):
63 def _import(self, key):
62 package = '.'.join(key.split('.')[0:-1])
64 package = '.'.join(key.split('.')[0:-1])
63 obj = key.split('.')[-1]
65 obj = key.split('.')[-1]
64 execString = 'from %s import %s' % (package, obj)
66 execString = 'from %s import %s' % (package, obj)
65 exec execString
67 exec execString
66 exec 'temp = %s' % obj
68 exec 'temp = %s' % obj
67 return temp
69 return temp
68
70
69 def resolve_file_path(self, filename, ipythondir = None):
71 def resolve_file_path(self, filename, ipythondir = None):
70 """Resolve filenames into absolute paths.
72 """Resolve filenames into absolute paths.
71
73
72 This function looks in the following directories in order:
74 This function looks in the following directories in order:
73
75
74 1. In the current working directory or by absolute path with ~ expanded
76 1. In the current working directory or by absolute path with ~ expanded
75 2. In ipythondir if that is set
77 2. In ipythondir if that is set
76 3. In the IPYTHONDIR environment variable if it exists
78 3. In the IPYTHONDIR environment variable if it exists
77 4. In the ~/.ipython directory
79 4. In the ~/.ipython directory
78
80
79 Note: The IPYTHONDIR is also used by the trunk version of IPython so
81 Note: The IPYTHONDIR is also used by the trunk version of IPython so
80 changing it will also affect it was well.
82 changing it will also affect it was well.
81 """
83 """
82
84
83 # In cwd or by absolute path with ~ expanded
85 # In cwd or by absolute path with ~ expanded
84 trythis = os.path.expanduser(filename)
86 trythis = os.path.expanduser(filename)
85 if os.path.isfile(trythis):
87 if os.path.isfile(trythis):
86 return trythis
88 return trythis
87
89
88 # In ipythondir if it is set
90 # In ipythondir if it is set
89 if ipythondir is not None:
91 if ipythondir is not None:
90 trythis = ipythondir + '/' + filename
92 trythis = pjoin(ipythondir, filename)
91 if os.path.isfile(trythis):
93 if os.path.isfile(trythis):
92 return trythis
94 return trythis
93
95
94 trythis = get_ipython_dir() + '/' + filename
96 trythis = pjoin(get_ipython_dir(), filename)
95 if os.path.isfile(trythis):
97 if os.path.isfile(trythis):
96 return trythis
98 return trythis
97
99
98 return None
100 return None
99
101
100
102
101
103
102
104
103
105
104
106
@@ -1,99 +1,34 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Configuration-related utilities for all IPython."""
3 """Configuration-related utilities for all IPython."""
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
18 import os
19 import sys
19 import sys
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # Normal code begins
22 # Normal code begins
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 class HomeDirError(Exception):
26 pass
27
28 def get_home_dir():
29 """Return the closest possible equivalent to a 'home' directory.
30
31 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
32
33 Currently only Posix and NT are implemented, a HomeDirError exception is
34 raised for all other OSes. """
35
36 isdir = os.path.isdir
37 env = os.environ
38 try:
39 homedir = env['HOME']
40 if not isdir(homedir):
41 # in case a user stuck some string which does NOT resolve to a
42 # valid path, it's as good as if we hadn't foud it
43 raise KeyError
44 return homedir
45 except KeyError:
46 if os.name == 'posix':
47 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
48 elif os.name == 'nt':
49 # For some strange reason, win9x returns 'nt' for os.name.
50 try:
51 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
52 if not isdir(homedir):
53 homedir = os.path.join(env['USERPROFILE'])
54 if not isdir(homedir):
55 raise HomeDirError
56 return homedir
57 except:
58 try:
59 # Use the registry to get the 'My Documents' folder.
60 import _winreg as wreg
61 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
62 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
63 homedir = wreg.QueryValueEx(key,'Personal')[0]
64 key.Close()
65 if not isdir(homedir):
66 e = ('Invalid "Personal" folder registry key '
67 'typically "My Documents".\n'
68 'Value: %s\n'
69 'This is not a valid directory on your system.' %
70 homedir)
71 raise HomeDirError(e)
72 return homedir
73 except HomeDirError:
74 raise
75 except:
76 return 'C:\\'
77 elif os.name == 'dos':
78 # Desperate, may do absurd things in classic MacOS. May work under DOS.
79 return 'C:\\'
80 else:
81 raise HomeDirError,'support for your operating system not implemented.'
82
83 def get_ipython_dir():
84 ipdir_def = '.ipython'
85 home_dir = get_home_dir()
86 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
87 os.path.join(home_dir,ipdir_def)))
88 return ipdir
89
90 def import_item(key):
25 def import_item(key):
91 """
26 """
92 Import and return bar given the string foo.bar.
27 Import and return bar given the string foo.bar.
93 """
28 """
94 package = '.'.join(key.split('.')[0:-1])
29 package = '.'.join(key.split('.')[0:-1])
95 obj = key.split('.')[-1]
30 obj = key.split('.')[-1]
96 execString = 'from %s import %s' % (package, obj)
31 execString = 'from %s import %s' % (package, obj)
97 exec execString
32 exec execString
98 exec 'temp = %s' % obj
33 exec 'temp = %s' % obj
99 return temp
34 return temp
@@ -1,168 +1,179 b''
1 # Addapted from killableprocess.py.
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
2 #______________________________________________________________________________
3 #
3 #
4 # killableprocess - subprocesses which can be reliably killed
4 # killableprocess - subprocesses which can be reliably killed
5 #
5 #
6 # Parts of this module are copied from the subprocess.py file contained
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
7 # in the Python distribution.
8 #
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
10 #
11 # Additions and modifications written by Benjamin Smedberg
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
13 # <http://www.mozilla.org/>
14 #
14 #
15 # By obtaining, using, and/or copying this software and/or its
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
17 # and will comply with the following terms and conditions:
18 #
18 #
19 # Permission to use, copy, modify, and distribute this software and
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
25 # distribution of the software without specific, written prior
26 # permission.
26 # permission.
27 #
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
35
36 r"""killableprocess - Subprocesses which can be reliably killed
36 r"""killableprocess - Subprocesses which can be reliably killed
37
37
38 This module is a subclass of the builtin "subprocess" module. It allows
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
40
41 It also adds a timeout argument to Wait() for a limited period of time before
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
42 forcefully killing the process.
43
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
47 """
48
48
49 import subprocess
49 import subprocess
50 from subprocess import PIPE
50 from subprocess import PIPE
51 import sys
51 import sys
52 import os
52 import os
53 import time
54 import types
53 import types
55
54
56 try:
55 try:
57 from subprocess import CalledProcessError
56 from subprocess import CalledProcessError
58 except ImportError:
57 except ImportError:
59 # Python 2.4 doesn't implement CalledProcessError
58 # Python 2.4 doesn't implement CalledProcessError
60 class CalledProcessError(Exception):
59 class CalledProcessError(Exception):
61 """This exception is raised when a process run by check_call() returns
60 """This exception is raised when a process run by check_call() returns
62 a non-zero exit status. The exit status will be stored in the
61 a non-zero exit status. The exit status will be stored in the
63 returncode attribute."""
62 returncode attribute."""
64 def __init__(self, returncode, cmd):
63 def __init__(self, returncode, cmd):
65 self.returncode = returncode
64 self.returncode = returncode
66 self.cmd = cmd
65 self.cmd = cmd
67 def __str__(self):
66 def __str__(self):
68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69
68
70 mswindows = (sys.platform == "win32")
69 mswindows = (sys.platform == "win32")
71
70
71 skip = False
72
72 if mswindows:
73 if mswindows:
73 import winprocess
74 import platform
75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
76 # Killable process does not work under vista when starting for
77 # something else than cmd.
78 skip = True
79 else:
80 import winprocess
74 else:
81 else:
75 import signal
82 import signal
76
83
77 if not mswindows:
84 if not mswindows:
78 def DoNothing(*args):
85 def DoNothing(*args):
79 pass
86 pass
80
87
81 class Popen(subprocess.Popen):
88
89 if skip:
90 Popen = subprocess.Popen
91 else:
92 class Popen(subprocess.Popen):
82 if not mswindows:
93 if not mswindows:
83 # Override __init__ to set a preexec_fn
94 # Override __init__ to set a preexec_fn
84 def __init__(self, *args, **kwargs):
95 def __init__(self, *args, **kwargs):
85 if len(args) >= 7:
96 if len(args) >= 7:
86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
97 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87
98
88 real_preexec_fn = kwargs.pop("preexec_fn", None)
99 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 def setpgid_preexec_fn():
100 def setpgid_preexec_fn():
90 os.setpgid(0, 0)
101 os.setpgid(0, 0)
91 if real_preexec_fn:
102 if real_preexec_fn:
92 apply(real_preexec_fn)
103 apply(real_preexec_fn)
93
104
94 kwargs['preexec_fn'] = setpgid_preexec_fn
105 kwargs['preexec_fn'] = setpgid_preexec_fn
95
106
96 subprocess.Popen.__init__(self, *args, **kwargs)
107 subprocess.Popen.__init__(self, *args, **kwargs)
97
108
98 if mswindows:
109 if mswindows:
99 def _execute_child(self, args, executable, preexec_fn, close_fds,
110 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 cwd, env, universal_newlines, startupinfo,
111 cwd, env, universal_newlines, startupinfo,
101 creationflags, shell,
112 creationflags, shell,
102 p2cread, p2cwrite,
113 p2cread, p2cwrite,
103 c2pread, c2pwrite,
114 c2pread, c2pwrite,
104 errread, errwrite):
115 errread, errwrite):
105 if not isinstance(args, types.StringTypes):
116 if not isinstance(args, types.StringTypes):
106 args = subprocess.list2cmdline(args)
117 args = subprocess.list2cmdline(args)
107
118
108 if startupinfo is None:
119 if startupinfo is None:
109 startupinfo = winprocess.STARTUPINFO()
120 startupinfo = winprocess.STARTUPINFO()
110
121
111 if None not in (p2cread, c2pwrite, errwrite):
122 if None not in (p2cread, c2pwrite, errwrite):
112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
123 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113
124
114 startupinfo.hStdInput = int(p2cread)
125 startupinfo.hStdInput = int(p2cread)
115 startupinfo.hStdOutput = int(c2pwrite)
126 startupinfo.hStdOutput = int(c2pwrite)
116 startupinfo.hStdError = int(errwrite)
127 startupinfo.hStdError = int(errwrite)
117 if shell:
128 if shell:
118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
129 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 startupinfo.wShowWindow = winprocess.SW_HIDE
130 startupinfo.wShowWindow = winprocess.SW_HIDE
120 comspec = os.environ.get("COMSPEC", "cmd.exe")
131 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 args = comspec + " /c " + args
132 args = comspec + " /c " + args
122
133
123 # We create a new job for this process, so that we can kill
134 # We create a new job for this process, so that we can kill
124 # the process and any sub-processes
135 # the process and any sub-processes
125 self._job = winprocess.CreateJobObject()
136 self._job = winprocess.CreateJobObject()
126
137
127 creationflags |= winprocess.CREATE_SUSPENDED
138 creationflags |= winprocess.CREATE_SUSPENDED
128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
139 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129
140
130 hp, ht, pid, tid = winprocess.CreateProcess(
141 hp, ht, pid, tid = winprocess.CreateProcess(
131 executable, args,
142 executable, args,
132 None, None, # No special security
143 None, None, # No special security
133 1, # Must inherit handles!
144 1, # Must inherit handles!
134 creationflags,
145 creationflags,
135 winprocess.EnvironmentBlock(env),
146 winprocess.EnvironmentBlock(env),
136 cwd, startupinfo)
147 cwd, startupinfo)
137
148
138 self._child_created = True
149 self._child_created = True
139 self._handle = hp
150 self._handle = hp
140 self._thread = ht
151 self._thread = ht
141 self.pid = pid
152 self.pid = pid
142
153
143 winprocess.AssignProcessToJobObject(self._job, hp)
154 winprocess.AssignProcessToJobObject(self._job, hp)
144 winprocess.ResumeThread(ht)
155 winprocess.ResumeThread(ht)
145
156
146 if p2cread is not None:
157 if p2cread is not None:
147 p2cread.Close()
158 p2cread.Close()
148 if c2pwrite is not None:
159 if c2pwrite is not None:
149 c2pwrite.Close()
160 c2pwrite.Close()
150 if errwrite is not None:
161 if errwrite is not None:
151 errwrite.Close()
162 errwrite.Close()
152
163
153 def kill(self, group=True):
164 def kill(self, group=True):
154 """Kill the process. If group=True, all sub-processes will also be killed."""
165 """Kill the process. If group=True, all sub-processes will also be killed."""
155 if mswindows:
166 if mswindows:
156 if group:
167 if group:
157 winprocess.TerminateJobObject(self._job, 127)
168 winprocess.TerminateJobObject(self._job, 127)
158 else:
169 else:
159 winprocess.TerminateProcess(self._handle, 127)
170 winprocess.TerminateProcess(self._handle, 127)
160 self.returncode = 127
171 self.returncode = 127
161 else:
172 else:
162 if group:
173 if group:
163 os.killpg(self.pid, signal.SIGKILL)
174 os.killpg(self.pid, signal.SIGKILL)
164 else:
175 else:
165 os.kill(self.pid, signal.SIGKILL)
176 os.kill(self.pid, signal.SIGKILL)
166 self.returncode = -9
177 self.returncode = -9
167
178
168
179
@@ -1,74 +1,74 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
4 stderr and stdin.
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 from killableprocess import Popen, PIPE
18 from killableprocess import Popen, PIPE
19 from threading import Thread
19 from threading import Thread
20 from time import sleep
20 from time import sleep
21 import os
21 import os
22
22
23 class PipedProcess(Thread):
23 class PipedProcess(Thread):
24 """ Class that encapsulates process execution by using callbacks for
24 """ Class that encapsulates process execution by using callbacks for
25 stdout, stderr and stdin, and providing a reliable way of
25 stdout, stderr and stdin, and providing a reliable way of
26 killing it.
26 killing it.
27 """
27 """
28
28
29 def __init__(self, command_string, out_callback,
29 def __init__(self, command_string, out_callback,
30 end_callback=None,):
30 end_callback=None,):
31 """ command_string: the command line executed to start the
31 """ command_string: the command line executed to start the
32 process.
32 process.
33
33
34 out_callback: the python callable called on stdout/stderr.
34 out_callback: the python callable called on stdout/stderr.
35
35
36 end_callback: an optional callable called when the process
36 end_callback: an optional callable called when the process
37 finishes.
37 finishes.
38
38
39 These callbacks are called from a different thread as the
39 These callbacks are called from a different thread as the
40 thread from which is started.
40 thread from which is started.
41 """
41 """
42 self.command_string = command_string
42 self.command_string = command_string
43 self.out_callback = out_callback
43 self.out_callback = out_callback
44 self.end_callback = end_callback
44 self.end_callback = end_callback
45 Thread.__init__(self)
45 Thread.__init__(self)
46
46
47
47
48 def run(self):
48 def run(self):
49 """ Start the process and hook up the callbacks.
49 """ Start the process and hook up the callbacks.
50 """
50 """
51 env = os.environ
51 env = os.environ
52 env['TERM'] = 'xterm'
52 env['TERM'] = 'xterm'
53 process = Popen((self.command_string + ' 2>&1', ), shell=True,
53 process = Popen(self.command_string + ' 2>&1', shell=True,
54 env=env,
54 env=env,
55 universal_newlines=True,
55 universal_newlines=True,
56 stdout=PIPE, stdin=PIPE, )
56 stdout=PIPE, stdin=PIPE, )
57 self.process = process
57 self.process = process
58 while True:
58 while True:
59 out_char = process.stdout.read(1)
59 out_char = process.stdout.read(1)
60 if out_char == '':
60 if out_char == '':
61 if process.poll() is not None:
61 if process.poll() is not None:
62 # The process has finished
62 # The process has finished
63 break
63 break
64 else:
64 else:
65 # The process is not giving any interesting
65 # The process is not giving any interesting
66 # output. No use polling it immediatly.
66 # output. No use polling it immediatly.
67 sleep(0.1)
67 sleep(0.1)
68 else:
68 else:
69 self.out_callback(out_char)
69 self.out_callback(out_char)
70
70
71 if self.end_callback is not None:
71 if self.end_callback is not None:
72 self.end_callback()
72 self.end_callback()
73
73
74
74
@@ -1,294 +1,320 b''
1 """
1 """
2 Base front end class for all line-oriented frontends, rather than
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
3 block-oriented.
4
4
5 Currently this focuses on synchronous frontends.
5 Currently this focuses on synchronous frontends.
6 """
6 """
7 __docformat__ = "restructuredtext en"
7 __docformat__ = "restructuredtext en"
8
8
9 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
10 # Copyright (C) 2008 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15
15
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import IPython
21 import IPython
22 import sys
22 import sys
23 import codeop
24 import traceback
23
25
24 from frontendbase import FrontEndBase
26 from frontendbase import FrontEndBase
25 from IPython.kernel.core.interpreter import Interpreter
27 from IPython.kernel.core.interpreter import Interpreter
26
28
27 def common_prefix(strings):
29 def common_prefix(strings):
28 """ Given a list of strings, return the common prefix between all
30 """ Given a list of strings, return the common prefix between all
29 these strings.
31 these strings.
30 """
32 """
31 ref = strings[0]
33 ref = strings[0]
32 prefix = ''
34 prefix = ''
33 for size in range(len(ref)):
35 for size in range(len(ref)):
34 test_prefix = ref[:size+1]
36 test_prefix = ref[:size+1]
35 for string in strings[1:]:
37 for string in strings[1:]:
36 if not string.startswith(test_prefix):
38 if not string.startswith(test_prefix):
37 return prefix
39 return prefix
38 prefix = test_prefix
40 prefix = test_prefix
39
41
40 return prefix
42 return prefix
41
43
42 #-------------------------------------------------------------------------------
44 #-------------------------------------------------------------------------------
43 # Base class for the line-oriented front ends
45 # Base class for the line-oriented front ends
44 #-------------------------------------------------------------------------------
46 #-------------------------------------------------------------------------------
45 class LineFrontEndBase(FrontEndBase):
47 class LineFrontEndBase(FrontEndBase):
46 """ Concrete implementation of the FrontEndBase class. This is meant
48 """ Concrete implementation of the FrontEndBase class. This is meant
47 to be the base class behind all the frontend that are line-oriented,
49 to be the base class behind all the frontend that are line-oriented,
48 rather than block-oriented.
50 rather than block-oriented.
49 """
51 """
50
52
51 # We need to keep the prompt number, to be able to increment
53 # We need to keep the prompt number, to be able to increment
52 # it when there is an exception.
54 # it when there is an exception.
53 prompt_number = 1
55 prompt_number = 1
54
56
55 # We keep a reference to the last result: it helps testing and
57 # We keep a reference to the last result: it helps testing and
56 # programatic control of the frontend.
58 # programatic control of the frontend.
57 last_result = dict(number=0)
59 last_result = dict(number=0)
58
60
59 # The input buffer being edited
61 # The input buffer being edited
60 input_buffer = ''
62 input_buffer = ''
61
63
62 # Set to true for debug output
64 # Set to true for debug output
63 debug = False
65 debug = False
64
66
65 # A banner to print at startup
67 # A banner to print at startup
66 banner = None
68 banner = None
67
69
68 #--------------------------------------------------------------------------
70 #--------------------------------------------------------------------------
69 # FrontEndBase interface
71 # FrontEndBase interface
70 #--------------------------------------------------------------------------
72 #--------------------------------------------------------------------------
71
73
72 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
74 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
73 if shell is None:
75 if shell is None:
74 shell = Interpreter()
76 shell = Interpreter()
75 FrontEndBase.__init__(self, shell=shell, history=history)
77 FrontEndBase.__init__(self, shell=shell, history=history)
76
78
77 if banner is not None:
79 if banner is not None:
78 self.banner = banner
80 self.banner = banner
81
82 def start(self):
83 """ Put the frontend in a state where it is ready for user
84 interaction.
85 """
79 if self.banner is not None:
86 if self.banner is not None:
80 self.write(self.banner, refresh=False)
87 self.write(self.banner, refresh=False)
81
88
82 self.new_prompt(self.input_prompt_template.substitute(number=1))
89 self.new_prompt(self.input_prompt_template.substitute(number=1))
83
90
84
91
85 def complete(self, line):
92 def complete(self, line):
86 """Complete line in engine's user_ns
93 """Complete line in engine's user_ns
87
94
88 Parameters
95 Parameters
89 ----------
96 ----------
90 line : string
97 line : string
91
98
92 Result
99 Result
93 ------
100 ------
94 The replacement for the line and the list of possible completions.
101 The replacement for the line and the list of possible completions.
95 """
102 """
96 completions = self.shell.complete(line)
103 completions = self.shell.complete(line)
97 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
104 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
98 if completions:
105 if completions:
99 prefix = common_prefix(completions)
106 prefix = common_prefix(completions)
100 residual = complete_sep.split(line)[:-1]
107 residual = complete_sep.split(line)[:-1]
101 line = line[:-len(residual)] + prefix
108 line = line[:-len(residual)] + prefix
102 return line, completions
109 return line, completions
103
110
104
111
105 def render_result(self, result):
112 def render_result(self, result):
106 """ Frontend-specific rendering of the result of a calculation
113 """ Frontend-specific rendering of the result of a calculation
107 that has been sent to an engine.
114 that has been sent to an engine.
108 """
115 """
109 if 'stdout' in result and result['stdout']:
116 if 'stdout' in result and result['stdout']:
110 self.write('\n' + result['stdout'])
117 self.write('\n' + result['stdout'])
111 if 'display' in result and result['display']:
118 if 'display' in result and result['display']:
112 self.write("%s%s\n" % (
119 self.write("%s%s\n" % (
113 self.output_prompt_template.substitute(
120 self.output_prompt_template.substitute(
114 number=result['number']),
121 number=result['number']),
115 result['display']['pprint']
122 result['display']['pprint']
116 ) )
123 ) )
117
124
118
125
119 def render_error(self, failure):
126 def render_error(self, failure):
120 """ Frontend-specific rendering of error.
127 """ Frontend-specific rendering of error.
121 """
128 """
122 self.write('\n\n'+str(failure)+'\n\n')
129 self.write('\n\n'+str(failure)+'\n\n')
123 return failure
130 return failure
124
131
125
132
126 def is_complete(self, string):
133 def is_complete(self, string):
127 """ Check if a string forms a complete, executable set of
134 """ Check if a string forms a complete, executable set of
128 commands.
135 commands.
129
136
130 For the line-oriented frontend, multi-line code is not executed
137 For the line-oriented frontend, multi-line code is not executed
131 as soon as it is complete: the users has to enter two line
138 as soon as it is complete: the users has to enter two line
132 returns.
139 returns.
133 """
140 """
134 if string in ('', '\n'):
141 if string in ('', '\n'):
135 # Prefiltering, eg through ipython0, may return an empty
142 # Prefiltering, eg through ipython0, may return an empty
136 # string although some operations have been accomplished. We
143 # string although some operations have been accomplished. We
137 # thus want to consider an empty string as a complete
144 # thus want to consider an empty string as a complete
138 # statement.
145 # statement.
139 return True
146 return True
140 elif ( len(self.input_buffer.split('\n'))>2
147 elif ( len(self.input_buffer.split('\n'))>2
141 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
148 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
142 return False
149 return False
143 else:
150 else:
144 # Add line returns here, to make sure that the statement is
151 self.capture_output()
145 # complete.
152 try:
146 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
153 # Add line returns here, to make sure that the statement is
154 # complete.
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 "<string>", "exec")
157 self.release_output()
158 except Exception, e:
159 # XXX: Hack: return True so that the
160 # code gets executed and the error captured.
161 is_complete = True
162 return is_complete
147
163
148
164
149 def write(self, string, refresh=True):
165 def write(self, string, refresh=True):
150 """ Write some characters to the display.
166 """ Write some characters to the display.
151
167
152 Subclass should overide this method.
168 Subclass should overide this method.
153
169
154 The refresh keyword argument is used in frontends with an
170 The refresh keyword argument is used in frontends with an
155 event loop, to choose whether the write should trigget an UI
171 event loop, to choose whether the write should trigget an UI
156 refresh, and thus be syncrhonous, or not.
172 refresh, and thus be syncrhonous, or not.
157 """
173 """
158 print >>sys.__stderr__, string
174 print >>sys.__stderr__, string
159
175
160
176
161 def execute(self, python_string, raw_string=None):
177 def execute(self, python_string, raw_string=None):
162 """ Stores the raw_string in the history, and sends the
178 """ Stores the raw_string in the history, and sends the
163 python string to the interpreter.
179 python string to the interpreter.
164 """
180 """
165 if raw_string is None:
181 if raw_string is None:
166 raw_string = python_string
182 raw_string = python_string
167 # Create a false result, in case there is an exception
183 # Create a false result, in case there is an exception
168 self.last_result = dict(number=self.prompt_number)
184 self.last_result = dict(number=self.prompt_number)
169 try:
185 try:
170 self.history.input_cache[-1] = raw_string.rstrip()
186 self.history.input_cache[-1] = raw_string.rstrip()
171 result = self.shell.execute(python_string)
187 result = self.shell.execute(python_string)
172 self.last_result = result
188 self.last_result = result
173 self.render_result(result)
189 self.render_result(result)
174 except:
190 except:
175 self.show_traceback()
191 self.show_traceback()
176 finally:
192 finally:
177 self.after_execute()
193 self.after_execute()
178
194
179 #--------------------------------------------------------------------------
195 #--------------------------------------------------------------------------
180 # LineFrontEndBase interface
196 # LineFrontEndBase interface
181 #--------------------------------------------------------------------------
197 #--------------------------------------------------------------------------
182
198
183 def prefilter_input(self, string):
199 def prefilter_input(self, string):
184 """ Priflter the input to turn it in valid python.
200 """ Prefilter the input to turn it in valid python.
185 """
201 """
186 string = string.replace('\r\n', '\n')
202 string = string.replace('\r\n', '\n')
187 string = string.replace('\t', 4*' ')
203 string = string.replace('\t', 4*' ')
188 # Clean the trailing whitespace
204 # Clean the trailing whitespace
189 string = '\n'.join(l.rstrip() for l in string.split('\n'))
205 string = '\n'.join(l.rstrip() for l in string.split('\n'))
190 return string
206 return string
191
207
192
208
193 def after_execute(self):
209 def after_execute(self):
194 """ All the operations required after an execution to put the
210 """ All the operations required after an execution to put the
195 terminal back in a shape where it is usable.
211 terminal back in a shape where it is usable.
196 """
212 """
197 self.prompt_number += 1
213 self.prompt_number += 1
198 self.new_prompt(self.input_prompt_template.substitute(
214 self.new_prompt(self.input_prompt_template.substitute(
199 number=(self.last_result['number'] + 1)))
215 number=(self.last_result['number'] + 1)))
200 # Start a new empty history entry
216 # Start a new empty history entry
201 self._add_history(None, '')
217 self._add_history(None, '')
202 self.history_cursor = len(self.history.input_cache) - 1
218 self.history_cursor = len(self.history.input_cache) - 1
203
219
204
220
205 def complete_current_input(self):
221 def complete_current_input(self):
206 """ Do code completion on current line.
222 """ Do code completion on current line.
207 """
223 """
208 if self.debug:
224 if self.debug:
209 print >>sys.__stdout__, "complete_current_input",
225 print >>sys.__stdout__, "complete_current_input",
210 line = self.input_buffer
226 line = self.input_buffer
211 new_line, completions = self.complete(line)
227 new_line, completions = self.complete(line)
212 if len(completions)>1:
228 if len(completions)>1:
213 self.write_completion(completions)
229 self.write_completion(completions, new_line=new_line)
214 self.input_buffer = new_line
230 elif not line == new_line:
231 self.input_buffer = new_line
215 if self.debug:
232 if self.debug:
233 print >>sys.__stdout__, 'line', line
234 print >>sys.__stdout__, 'new_line', new_line
216 print >>sys.__stdout__, completions
235 print >>sys.__stdout__, completions
217
236
218
237
219 def get_line_width(self):
238 def get_line_width(self):
220 """ Return the width of the line in characters.
239 """ Return the width of the line in characters.
221 """
240 """
222 return 80
241 return 80
223
242
224
243
225 def write_completion(self, possibilities):
244 def write_completion(self, possibilities, new_line=None):
226 """ Write the list of possible completions.
245 """ Write the list of possible completions.
246
247 new_line is the completed input line that should be displayed
248 after the completion are writen. If None, the input_buffer
249 before the completion is used.
227 """
250 """
228 current_buffer = self.input_buffer
251 if new_line is None:
252 new_line = self.input_buffer
229
253
230 self.write('\n')
254 self.write('\n')
231 max_len = len(max(possibilities, key=len)) + 1
255 max_len = len(max(possibilities, key=len)) + 1
232
256
233 # Now we check how much symbol we can put on a line...
257 # Now we check how much symbol we can put on a line...
234 chars_per_line = self.get_line_width()
258 chars_per_line = self.get_line_width()
235 symbols_per_line = max(1, chars_per_line/max_len)
259 symbols_per_line = max(1, chars_per_line/max_len)
236
260
237 pos = 1
261 pos = 1
238 buf = []
262 buf = []
239 for symbol in possibilities:
263 for symbol in possibilities:
240 if pos < symbols_per_line:
264 if pos < symbols_per_line:
241 buf.append(symbol.ljust(max_len))
265 buf.append(symbol.ljust(max_len))
242 pos += 1
266 pos += 1
243 else:
267 else:
244 buf.append(symbol.rstrip() + '\n')
268 buf.append(symbol.rstrip() + '\n')
245 pos = 1
269 pos = 1
246 self.write(''.join(buf))
270 self.write(''.join(buf))
247 self.new_prompt(self.input_prompt_template.substitute(
271 self.new_prompt(self.input_prompt_template.substitute(
248 number=self.last_result['number'] + 1))
272 number=self.last_result['number'] + 1))
249 self.input_buffer = current_buffer
273 self.input_buffer = new_line
250
274
251
275
252 def new_prompt(self, prompt):
276 def new_prompt(self, prompt):
253 """ Prints a prompt and starts a new editing buffer.
277 """ Prints a prompt and starts a new editing buffer.
254
278
255 Subclasses should use this method to make sure that the
279 Subclasses should use this method to make sure that the
256 terminal is put in a state favorable for a new line
280 terminal is put in a state favorable for a new line
257 input.
281 input.
258 """
282 """
259 self.input_buffer = ''
283 self.input_buffer = ''
260 self.write(prompt)
284 self.write(prompt)
261
285
262
286
263 #--------------------------------------------------------------------------
287 #--------------------------------------------------------------------------
264 # Private API
288 # Private API
265 #--------------------------------------------------------------------------
289 #--------------------------------------------------------------------------
266
290
267 def _on_enter(self):
291 def _on_enter(self):
268 """ Called when the return key is pressed in a line editing
292 """ Called when the return key is pressed in a line editing
269 buffer.
293 buffer.
270 """
294 """
271 current_buffer = self.input_buffer
295 current_buffer = self.input_buffer
272 cleaned_buffer = self.prefilter_input(current_buffer)
296 cleaned_buffer = self.prefilter_input(current_buffer)
273 if self.is_complete(cleaned_buffer):
297 if self.is_complete(cleaned_buffer):
274 self.execute(cleaned_buffer, raw_string=current_buffer)
298 self.execute(cleaned_buffer, raw_string=current_buffer)
275 else:
299 else:
276 self.input_buffer += self._get_indent_string(
300 self.input_buffer += self._get_indent_string(
277 current_buffer[:-1])
301 current_buffer[:-1])
302 if len(current_buffer.split('\n')) == 2:
303 self.input_buffer += '\t\t'
278 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
304 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
279 self.input_buffer += '\t'
305 self.input_buffer += '\t'
280
306
281
307
282 def _get_indent_string(self, string):
308 def _get_indent_string(self, string):
283 """ Return the string of whitespace that prefixes a line. Used to
309 """ Return the string of whitespace that prefixes a line. Used to
284 add the right amount of indendation when creating a new line.
310 add the right amount of indendation when creating a new line.
285 """
311 """
286 string = string.replace('\t', ' '*4)
312 string = string.replace('\t', ' '*4)
287 string = string.split('\n')[-1]
313 string = string.split('\n')[-1]
288 indent_chars = len(string) - len(string.lstrip())
314 indent_chars = len(string) - len(string.lstrip())
289 indent_string = '\t'*(indent_chars // 4) + \
315 indent_string = '\t'*(indent_chars // 4) + \
290 ' '*(indent_chars % 4)
316 ' '*(indent_chars % 4)
291
317
292 return indent_string
318 return indent_string
293
319
294
320
@@ -1,223 +1,230 b''
1 """
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
2 Frontend class that uses IPython0 to prefilter the inputs.
3
3
4 Using the IPython0 mechanism gives us access to the magics.
4 Using the IPython0 mechanism gives us access to the magics.
5
5
6 This is a transitory class, used here to do the transition between
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
10 refactoring.
11 """
11 """
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 from linefrontendbase import LineFrontEndBase, common_prefix
27 from frontendbase import FrontEndBase
27
28
28 from IPython.ipmaker import make_IPython
29 from IPython.ipmaker import make_IPython
29 from IPython.ipapi import IPApi
30 from IPython.ipapi import IPApi
30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31
32
32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33
34
34 from IPython.genutils import Term
35 from IPython.genutils import Term
35 import pydoc
36 import pydoc
36 import os
37 import os
38 import sys
37
39
38
40
39 def mk_system_call(system_call_function, command):
41 def mk_system_call(system_call_function, command):
40 """ given a os.system replacement, and a leading string command,
42 """ given a os.system replacement, and a leading string command,
41 returns a function that will execute the command with the given
43 returns a function that will execute the command with the given
42 argument string.
44 argument string.
43 """
45 """
44 def my_system_call(args):
46 def my_system_call(args):
45 system_call_function("%s %s" % (command, args))
47 system_call_function("%s %s" % (command, args))
46 return my_system_call
48 return my_system_call
47
49
48 #-------------------------------------------------------------------------------
50 #-------------------------------------------------------------------------------
49 # Frontend class using ipython0 to do the prefiltering.
51 # Frontend class using ipython0 to do the prefiltering.
50 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
51 class PrefilterFrontEnd(LineFrontEndBase):
53 class PrefilterFrontEnd(LineFrontEndBase):
52 """ Class that uses ipython0 to do prefilter the input, do the
54 """ Class that uses ipython0 to do prefilter the input, do the
53 completion and the magics.
55 completion and the magics.
54
56
55 The core trick is to use an ipython0 instance to prefilter the
57 The core trick is to use an ipython0 instance to prefilter the
56 input, and share the namespace between the interpreter instance used
58 input, and share the namespace between the interpreter instance used
57 to execute the statements and the ipython0 used for code
59 to execute the statements and the ipython0 used for code
58 completion...
60 completion...
59 """
61 """
62
63 debug = False
60
64
61 def __init__(self, ipython0=None, *args, **kwargs):
65 def __init__(self, ipython0=None, *args, **kwargs):
62 """ Parameters:
66 """ Parameters:
63 -----------
67 -----------
64
68
65 ipython0: an optional ipython0 instance to use for command
69 ipython0: an optional ipython0 instance to use for command
66 prefiltering and completion.
70 prefiltering and completion.
67 """
71 """
72 LineFrontEndBase.__init__(self, *args, **kwargs)
73 self.shell.output_trap = RedirectorOutputTrap(
74 out_callback=self.write,
75 err_callback=self.write,
76 )
77 self.shell.traceback_trap = SyncTracebackTrap(
78 formatters=self.shell.traceback_trap.formatters,
79 )
80
81 # Start the ipython0 instance:
68 self.save_output_hooks()
82 self.save_output_hooks()
69 if ipython0 is None:
83 if ipython0 is None:
70 # Instanciate an IPython0 interpreter to be able to use the
84 # Instanciate an IPython0 interpreter to be able to use the
71 # prefiltering.
85 # prefiltering.
72 # XXX: argv=[] is a bit bold.
86 # XXX: argv=[] is a bit bold.
73 ipython0 = make_IPython(argv=[])
87 ipython0 = make_IPython(argv=[],
88 user_ns=self.shell.user_ns,
89 user_global_ns=self.shell.user_global_ns)
74 self.ipython0 = ipython0
90 self.ipython0 = ipython0
75 # Set the pager:
91 # Set the pager:
76 self.ipython0.set_hook('show_in_pager',
92 self.ipython0.set_hook('show_in_pager',
77 lambda s, string: self.write("\n" + string))
93 lambda s, string: self.write("\n" + string))
78 self.ipython0.write = self.write
94 self.ipython0.write = self.write
79 self._ip = _ip = IPApi(self.ipython0)
95 self._ip = _ip = IPApi(self.ipython0)
80 # Make sure the raw system call doesn't get called, as we don't
96 # Make sure the raw system call doesn't get called, as we don't
81 # have a stdin accessible.
97 # have a stdin accessible.
82 self._ip.system = self.system_call
98 self._ip.system = self.system_call
83 # XXX: Muck around with magics so that they work better
99 # XXX: Muck around with magics so that they work better
84 # in our environment
100 # in our environment
85 self.ipython0.magic_ls = mk_system_call(self.system_call,
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
86 'ls -CF')
102 'ls -CF')
87 # And now clean up the mess created by ipython0
103 # And now clean up the mess created by ipython0
88 self.release_output()
104 self.release_output()
105
106
89 if not 'banner' in kwargs and self.banner is None:
107 if not 'banner' in kwargs and self.banner is None:
90 kwargs['banner'] = self.ipython0.BANNER + """
108 self.banner = self.ipython0.BANNER + """
91 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
92
110
93 LineFrontEndBase.__init__(self, *args, **kwargs)
111 self.start()
94 # XXX: Hack: mix the two namespaces
95 self.shell.user_ns.update(self.ipython0.user_ns)
96 self.ipython0.user_ns = self.shell.user_ns
97 self.shell.user_global_ns.update(self.ipython0.user_global_ns)
98 self.ipython0.user_global_ns = self.shell.user_global_ns
99
100 self.shell.output_trap = RedirectorOutputTrap(
101 out_callback=self.write,
102 err_callback=self.write,
103 )
104 self.shell.traceback_trap = SyncTracebackTrap(
105 formatters=self.shell.traceback_trap.formatters,
106 )
107
112
108 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
109 # FrontEndBase interface
114 # FrontEndBase interface
110 #--------------------------------------------------------------------------
115 #--------------------------------------------------------------------------
111
116
112 def show_traceback(self):
117 def show_traceback(self):
113 """ Use ipython0 to capture the last traceback and display it.
118 """ Use ipython0 to capture the last traceback and display it.
114 """
119 """
115 self.capture_output()
120 self.capture_output()
116 self.ipython0.showtraceback()
121 self.ipython0.showtraceback(tb_offset=-1)
117 self.release_output()
122 self.release_output()
118
123
119
124
120 def execute(self, python_string, raw_string=None):
125 def execute(self, python_string, raw_string=None):
121 if self.debug:
126 if self.debug:
122 print 'Executing Python code:', repr(python_string)
127 print 'Executing Python code:', repr(python_string)
123 self.capture_output()
128 self.capture_output()
124 LineFrontEndBase.execute(self, python_string,
129 LineFrontEndBase.execute(self, python_string,
125 raw_string=raw_string)
130 raw_string=raw_string)
126 self.release_output()
131 self.release_output()
127
132
128
133
129 def save_output_hooks(self):
134 def save_output_hooks(self):
130 """ Store all the output hooks we can think of, to be able to
135 """ Store all the output hooks we can think of, to be able to
131 restore them.
136 restore them.
132
137
133 We need to do this early, as starting the ipython0 instance will
138 We need to do this early, as starting the ipython0 instance will
134 screw ouput hooks.
139 screw ouput hooks.
135 """
140 """
136 self.__old_cout_write = Term.cout.write
141 self.__old_cout_write = Term.cout.write
137 self.__old_cerr_write = Term.cerr.write
142 self.__old_cerr_write = Term.cerr.write
138 self.__old_stdout = sys.stdout
143 self.__old_stdout = sys.stdout
139 self.__old_stderr= sys.stderr
144 self.__old_stderr= sys.stderr
140 self.__old_help_output = pydoc.help.output
145 self.__old_help_output = pydoc.help.output
141 self.__old_display_hook = sys.displayhook
146 self.__old_display_hook = sys.displayhook
142
147
143
148
144 def capture_output(self):
149 def capture_output(self):
145 """ Capture all the output mechanisms we can think of.
150 """ Capture all the output mechanisms we can think of.
146 """
151 """
147 self.save_output_hooks()
152 self.save_output_hooks()
148 Term.cout.write = self.write
153 Term.cout.write = self.write
149 Term.cerr.write = self.write
154 Term.cerr.write = self.write
150 sys.stdout = Term.cout
155 sys.stdout = Term.cout
151 sys.stderr = Term.cerr
156 sys.stderr = Term.cerr
152 pydoc.help.output = self.shell.output_trap.out
157 pydoc.help.output = self.shell.output_trap.out
153
158
154
159
155 def release_output(self):
160 def release_output(self):
156 """ Release all the different captures we have made.
161 """ Release all the different captures we have made.
157 """
162 """
158 Term.cout.write = self.__old_cout_write
163 Term.cout.write = self.__old_cout_write
159 Term.cerr.write = self.__old_cerr_write
164 Term.cerr.write = self.__old_cerr_write
160 sys.stdout = self.__old_stdout
165 sys.stdout = self.__old_stdout
161 sys.stderr = self.__old_stderr
166 sys.stderr = self.__old_stderr
162 pydoc.help.output = self.__old_help_output
167 pydoc.help.output = self.__old_help_output
163 sys.displayhook = self.__old_display_hook
168 sys.displayhook = self.__old_display_hook
164
169
165
170
166 def complete(self, line):
171 def complete(self, line):
172 # FIXME: This should be factored out in the linefrontendbase
173 # method.
167 word = line.split('\n')[-1].split(' ')[-1]
174 word = line.split('\n')[-1].split(' ')[-1]
168 completions = self.ipython0.complete(word)
175 completions = self.ipython0.complete(word)
169 # FIXME: The proper sort should be done in the complete method.
176 # FIXME: The proper sort should be done in the complete method.
170 key = lambda x: x.replace('_', '')
177 key = lambda x: x.replace('_', '')
171 completions.sort(key=key)
178 completions.sort(key=key)
172 if completions:
179 if completions:
173 prefix = common_prefix(completions)
180 prefix = common_prefix(completions)
174 line = line[:-len(word)] + prefix
181 line = line[:-len(word)] + prefix
175 return line, completions
182 return line, completions
176
183
177
184
178 #--------------------------------------------------------------------------
185 #--------------------------------------------------------------------------
179 # LineFrontEndBase interface
186 # LineFrontEndBase interface
180 #--------------------------------------------------------------------------
187 #--------------------------------------------------------------------------
181
188
182 def prefilter_input(self, input_string):
189 def prefilter_input(self, input_string):
183 """ Using IPython0 to prefilter the commands to turn them
190 """ Using IPython0 to prefilter the commands to turn them
184 in executable statements that are valid Python strings.
191 in executable statements that are valid Python strings.
185 """
192 """
186 input_string = LineFrontEndBase.prefilter_input(self, input_string)
193 input_string = LineFrontEndBase.prefilter_input(self, input_string)
187 filtered_lines = []
194 filtered_lines = []
188 # The IPython0 prefilters sometime produce output. We need to
195 # The IPython0 prefilters sometime produce output. We need to
189 # capture it.
196 # capture it.
190 self.capture_output()
197 self.capture_output()
191 self.last_result = dict(number=self.prompt_number)
198 self.last_result = dict(number=self.prompt_number)
192 try:
199 try:
193 for line in input_string.split('\n'):
200 for line in input_string.split('\n'):
194 filtered_lines.append(
201 filtered_lines.append(
195 self.ipython0.prefilter(line, False).rstrip())
202 self.ipython0.prefilter(line, False).rstrip())
196 except:
203 except:
197 # XXX: probably not the right thing to do.
204 # XXX: probably not the right thing to do.
198 self.ipython0.showsyntaxerror()
205 self.ipython0.showsyntaxerror()
199 self.after_execute()
206 self.after_execute()
200 finally:
207 finally:
201 self.release_output()
208 self.release_output()
202
209
203 # Clean up the trailing whitespace, to avoid indentation errors
210 # Clean up the trailing whitespace, to avoid indentation errors
204 filtered_string = '\n'.join(filtered_lines)
211 filtered_string = '\n'.join(filtered_lines)
205 return filtered_string
212 return filtered_string
206
213
207
214
208 #--------------------------------------------------------------------------
215 #--------------------------------------------------------------------------
209 # PrefilterFrontEnd interface
216 # PrefilterFrontEnd interface
210 #--------------------------------------------------------------------------
217 #--------------------------------------------------------------------------
211
218
212 def system_call(self, command_string):
219 def system_call(self, command_string):
213 """ Allows for frontend to define their own system call, to be
220 """ Allows for frontend to define their own system call, to be
214 able capture output and redirect input.
221 able capture output and redirect input.
215 """
222 """
216 return os.system(command_string)
223 return os.system(command_string)
217
224
218
225
219 def do_exit(self):
226 def do_exit(self):
220 """ Exit the shell, cleanup and save the history.
227 """ Exit the shell, cleanup and save the history.
221 """
228 """
222 self.ipython0.atexit_operations()
229 self.ipython0.atexit_operations()
223
230
@@ -1,157 +1,155 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the frontendbase module."""
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 unittest
18 import unittest
19
19
20 try:
20 try:
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
22 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
23 from IPython.kernel.engineservice import EngineService
24 except ImportError:
24 except ImportError:
25 import nose
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
27
28 from IPython.testing.decorators import skip
29
28 class FrontEndCallbackChecker(AsyncFrontEndBase):
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
29 """FrontEndBase subclass for checking callbacks"""
31 """FrontEndBase subclass for checking callbacks"""
30 def __init__(self, engine=None, history=None):
32 def __init__(self, engine=None, history=None):
31 super(FrontEndCallbackChecker, self).__init__(engine=engine,
33 super(FrontEndCallbackChecker, self).__init__(engine=engine,
32 history=history)
34 history=history)
33 self.updateCalled = False
35 self.updateCalled = False
34 self.renderResultCalled = False
36 self.renderResultCalled = False
35 self.renderErrorCalled = False
37 self.renderErrorCalled = False
36
38
37 def update_cell_prompt(self, result, blockID=None):
39 def update_cell_prompt(self, result, blockID=None):
38 self.updateCalled = True
40 self.updateCalled = True
39 return result
41 return result
40
42
41 def render_result(self, result):
43 def render_result(self, result):
42 self.renderResultCalled = True
44 self.renderResultCalled = True
43 return result
45 return result
44
46
45
47
46 def render_error(self, failure):
48 def render_error(self, failure):
47 self.renderErrorCalled = True
49 self.renderErrorCalled = True
48 return failure
50 return failure
49
51
50
52
51
53
52
54
53 class TestAsyncFrontendBase(unittest.TestCase):
55 class TestAsyncFrontendBase(unittest.TestCase):
54 def setUp(self):
56 def setUp(self):
55 """Setup the EngineService and FrontEndBase"""
57 """Setup the EngineService and FrontEndBase"""
56
58
57 self.fb = FrontEndCallbackChecker(engine=EngineService())
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
58
60
59
60 def test_implements_IFrontEnd(self):
61 def test_implements_IFrontEnd(self):
61 assert(frontendbase.IFrontEnd.implementedBy(
62 assert(frontendbase.IFrontEnd.implementedBy(
62 AsyncFrontEndBase))
63 AsyncFrontEndBase))
63
64
64
65 def test_is_complete_returns_False_for_incomplete_block(self):
65 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
66 """"""
67
67
68 block = """def test(a):"""
68 block = """def test(a):"""
69
69
70 assert(self.fb.is_complete(block) == False)
70 assert(self.fb.is_complete(block) == False)
71
71
72 def test_is_complete_returns_True_for_complete_block(self):
72 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
73 """"""
74
74
75 block = """def test(a): pass"""
75 block = """def test(a): pass"""
76
76
77 assert(self.fb.is_complete(block))
77 assert(self.fb.is_complete(block))
78
78
79 block = """a=3"""
79 block = """a=3"""
80
80
81 assert(self.fb.is_complete(block))
81 assert(self.fb.is_complete(block))
82
82
83
84 def test_blockID_added_to_result(self):
83 def test_blockID_added_to_result(self):
85 block = """3+3"""
84 block = """3+3"""
86
85
87 d = self.fb.execute(block, blockID='TEST_ID')
86 d = self.fb.execute(block, blockID='TEST_ID')
88
87
89 d.addCallback(self.checkBlockID, expected='TEST_ID')
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
90
89
91 def test_blockID_added_to_failure(self):
90 def test_blockID_added_to_failure(self):
92 block = "raise Exception()"
91 block = "raise Exception()"
93
92
94 d = self.fb.execute(block,blockID='TEST_ID')
93 d = self.fb.execute(block,blockID='TEST_ID')
95 d.addErrback(self.checkFailureID, expected='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
96
95
97 def checkBlockID(self, result, expected=""):
96 def checkBlockID(self, result, expected=""):
98 assert(result['blockID'] == expected)
97 assert(result['blockID'] == expected)
99
98
100
99
101 def checkFailureID(self, failure, expected=""):
100 def checkFailureID(self, failure, expected=""):
102 assert(failure.blockID == expected)
101 assert(failure.blockID == expected)
103
102
104
103
105 def test_callbacks_added_to_execute(self):
104 def test_callbacks_added_to_execute(self):
106 """test that
105 """test that
107 update_cell_prompt
106 update_cell_prompt
108 render_result
107 render_result
109
108
110 are added to execute request
109 are added to execute request
111 """
110 """
112
111
113 d = self.fb.execute("10+10")
112 d = self.fb.execute("10+10")
114 d.addCallback(self.checkCallbacks)
113 d.addCallback(self.checkCallbacks)
115
114
116
117 def checkCallbacks(self, result):
115 def checkCallbacks(self, result):
118 assert(self.fb.updateCalled)
116 assert(self.fb.updateCalled)
119 assert(self.fb.renderResultCalled)
117 assert(self.fb.renderResultCalled)
120
118
121
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
122 def test_error_callback_added_to_execute(self):
120 def test_error_callback_added_to_execute(self):
123 """test that render_error called on execution error"""
121 """test that render_error called on execution error"""
124
122
125 d = self.fb.execute("raise Exception()")
123 d = self.fb.execute("raise Exception()")
126 d.addCallback(self.checkRenderError)
124 d.addCallback(self.checkRenderError)
127
125
128 def checkRenderError(self, result):
126 def checkRenderError(self, result):
129 assert(self.fb.renderErrorCalled)
127 assert(self.fb.renderErrorCalled)
130
128
131 def test_history_returns_expected_block(self):
129 def test_history_returns_expected_block(self):
132 """Make sure history browsing doesn't fail"""
130 """Make sure history browsing doesn't fail"""
133
131
134 blocks = ["a=1","a=2","a=3"]
132 blocks = ["a=1","a=2","a=3"]
135 for b in blocks:
133 for b in blocks:
136 d = self.fb.execute(b)
134 d = self.fb.execute(b)
137
135
138 # d is now the deferred for the last executed block
136 # d is now the deferred for the last executed block
139 d.addCallback(self.historyTests, blocks)
137 d.addCallback(self.historyTests, blocks)
140
138
141
139
142 def historyTests(self, result, blocks):
140 def historyTests(self, result, blocks):
143 """historyTests"""
141 """historyTests"""
144
142
145 assert(len(blocks) >= 3)
143 assert(len(blocks) >= 3)
146 assert(self.fb.get_history_previous("") == blocks[-2])
144 assert(self.fb.get_history_previous("") == blocks[-2])
147 assert(self.fb.get_history_previous("") == blocks[-3])
145 assert(self.fb.get_history_previous("") == blocks[-3])
148 assert(self.fb.get_history_next() == blocks[-2])
146 assert(self.fb.get_history_next() == blocks[-2])
149
147
150
148
151 def test_history_returns_none_at_startup(self):
149 def test_history_returns_none_at_startup(self):
152 """test_history_returns_none_at_startup"""
150 """test_history_returns_none_at_startup"""
153
151
154 assert(self.fb.get_history_previous("")==None)
152 assert(self.fb.get_history_previous("")==None)
155 assert(self.fb.get_history_next()==None)
153 assert(self.fb.get_history_next()==None)
156
154
157
155
@@ -1,67 +1,67 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Test process execution and IO redirection.
3 Test process execution and IO redirection.
4 """
4 """
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
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 from cStringIO import StringIO
15 from cStringIO import StringIO
16 from time import sleep
16 from time import sleep
17 import sys
17 import sys
18
18
19 from IPython.frontend._process import PipedProcess
19 from IPython.frontend._process import PipedProcess
20 from IPython.testing import decorators as testdec
20 from IPython.testing import decorators as testdec
21
21
22
22 def test_capture_out():
23 def test_capture_out():
23 """ A simple test to see if we can execute a process and get the output.
24 """ A simple test to see if we can execute a process and get the output.
24 """
25 """
25 s = StringIO()
26 s = StringIO()
26 p = PipedProcess('echo 1', out_callback=s.write, )
27 p = PipedProcess('echo 1', out_callback=s.write, )
27 p.start()
28 p.start()
28 p.join()
29 p.join()
29 result = s.getvalue().rstrip()
30 result = s.getvalue().rstrip()
30 assert result == '1'
31 assert result == '1'
31
32
32 # FIXME
33
33 @testdec.skip("This doesn't work under Windows")
34 def test_io():
34 def test_io():
35 """ Checks that we can send characters on stdin to the process.
35 """ Checks that we can send characters on stdin to the process.
36 """
36 """
37 s = StringIO()
37 s = StringIO()
38 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
38 p = PipedProcess(sys.executable + ' -c "a = raw_input(); print a"',
39 out_callback=s.write, )
39 out_callback=s.write, )
40 p.start()
40 p.start()
41 test_string = '12345\n'
41 test_string = '12345\n'
42 while not hasattr(p, 'process'):
42 while not hasattr(p, 'process'):
43 sleep(0.1)
43 sleep(0.1)
44 p.process.stdin.write(test_string)
44 p.process.stdin.write(test_string)
45 p.join()
45 p.join()
46 result = s.getvalue()
46 result = s.getvalue()
47 assert result == test_string
47 assert result == test_string
48
48
49
49
50 def test_kill():
50 def test_kill():
51 """ Check that we can kill a process, and its subprocess.
51 """ Check that we can kill a process, and its subprocess.
52 """
52 """
53 s = StringIO()
53 s = StringIO()
54 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
54 p = PipedProcess(sys.executable + ' -c "a = raw_input();"',
55 out_callback=s.write, )
55 out_callback=s.write, )
56 p.start()
56 p.start()
57 while not hasattr(p, 'process'):
57 while not hasattr(p, 'process'):
58 sleep(0.1)
58 sleep(0.1)
59 p.process.kill()
59 p.process.kill()
60 assert p.process.poll() is not None
60 assert p.process.poll() is not None
61
61
62
62
63 if __name__ == '__main__':
63 if __name__ == '__main__':
64 test_capture_out()
64 test_capture_out()
65 test_io()
65 test_io()
66 test_kill()
66 test_kill()
67
67
@@ -1,428 +1,436 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A Wx widget to act as a console and input commands.
3 A Wx widget to act as a console and input commands.
4
4
5 This widget deals with prompts and provides an edit buffer
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
6 restricted to after the last prompt.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 import wx
22 import wx
23 import wx.stc as stc
23 import wx.stc as stc
24
24
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import time
26 import sys
27 import sys
27 LINESEP = '\n'
28 LINESEP = '\n'
28 if sys.platform == 'win32':
29 if sys.platform == 'win32':
29 LINESEP = '\n\r'
30 LINESEP = '\n\r'
30
31
31 import re
32 import re
32
33
33 # FIXME: Need to provide an API for non user-generated display on the
34 # FIXME: Need to provide an API for non user-generated display on the
34 # screen: this should not be editable by the user.
35 # screen: this should not be editable by the user.
35
36
36 _DEFAULT_SIZE = 10
37 _DEFAULT_SIZE = 10
37 if sys.platform == 'darwin':
38 if sys.platform == 'darwin':
38 _DEFAULT_STYLE = 12
39 _DEFAULT_STYLE = 12
39
40
40 _DEFAULT_STYLE = {
41 _DEFAULT_STYLE = {
41 'stdout' : 'fore:#0000FF',
42 'stdout' : 'fore:#0000FF',
42 'stderr' : 'fore:#007f00',
43 'stderr' : 'fore:#007f00',
43 'trace' : 'fore:#FF0000',
44 'trace' : 'fore:#FF0000',
44
45
45 'default' : 'size:%d' % _DEFAULT_SIZE,
46 'default' : 'size:%d' % _DEFAULT_SIZE,
46 'bracegood' : 'fore:#00AA00,back:#000000,bold',
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
47 'bracebad' : 'fore:#FF0000,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
48
49
49 # properties for the various Python lexer styles
50 # properties for the various Python lexer styles
50 'comment' : 'fore:#007F00',
51 'comment' : 'fore:#007F00',
51 'number' : 'fore:#007F7F',
52 'number' : 'fore:#007F7F',
52 'string' : 'fore:#7F007F,italic',
53 'string' : 'fore:#7F007F,italic',
53 'char' : 'fore:#7F007F,italic',
54 'char' : 'fore:#7F007F,italic',
54 'keyword' : 'fore:#00007F,bold',
55 'keyword' : 'fore:#00007F,bold',
55 'triple' : 'fore:#7F0000',
56 'triple' : 'fore:#7F0000',
56 'tripledouble' : 'fore:#7F0000',
57 'tripledouble' : 'fore:#7F0000',
57 'class' : 'fore:#0000FF,bold,underline',
58 'class' : 'fore:#0000FF,bold,underline',
58 'def' : 'fore:#007F7F,bold',
59 'def' : 'fore:#007F7F,bold',
59 'operator' : 'bold'
60 'operator' : 'bold'
60 }
61 }
61
62
62 # new style numbers
63 # new style numbers
63 _STDOUT_STYLE = 15
64 _STDOUT_STYLE = 15
64 _STDERR_STYLE = 16
65 _STDERR_STYLE = 16
65 _TRACE_STYLE = 17
66 _TRACE_STYLE = 17
66
67
67
68
68 # system colors
69 # system colors
69 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
70
71
71 #-------------------------------------------------------------------------------
72 #-------------------------------------------------------------------------------
72 # The console widget class
73 # The console widget class
73 #-------------------------------------------------------------------------------
74 #-------------------------------------------------------------------------------
74 class ConsoleWidget(editwindow.EditWindow):
75 class ConsoleWidget(editwindow.EditWindow):
75 """ Specialized styled text control view for console-like workflow.
76 """ Specialized styled text control view for console-like workflow.
76
77
77 This widget is mainly interested in dealing with the prompt and
78 This widget is mainly interested in dealing with the prompt and
78 keeping the cursor inside the editing line.
79 keeping the cursor inside the editing line.
79 """
80 """
80
81
81 # This is where the title captured from the ANSI escape sequences are
82 # This is where the title captured from the ANSI escape sequences are
82 # stored.
83 # stored.
83 title = 'Console'
84 title = 'Console'
84
85
85 # The buffer being edited.
86 # The buffer being edited.
86 def _set_input_buffer(self, string):
87 def _set_input_buffer(self, string):
87 self.SetSelection(self.current_prompt_pos, self.GetLength())
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
88 self.ReplaceSelection(string)
89 self.ReplaceSelection(string)
89 self.GotoPos(self.GetLength())
90 self.GotoPos(self.GetLength())
90
91
91 def _get_input_buffer(self):
92 def _get_input_buffer(self):
92 """ Returns the text in current edit buffer.
93 """ Returns the text in current edit buffer.
93 """
94 """
94 input_buffer = self.GetTextRange(self.current_prompt_pos,
95 input_buffer = self.GetTextRange(self.current_prompt_pos,
95 self.GetLength())
96 self.GetLength())
96 input_buffer = input_buffer.replace(LINESEP, '\n')
97 input_buffer = input_buffer.replace(LINESEP, '\n')
97 return input_buffer
98 return input_buffer
98
99
99 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
100
101
101 style = _DEFAULT_STYLE.copy()
102 style = _DEFAULT_STYLE.copy()
102
103
103 # Translation table from ANSI escape sequences to color. Override
104 # Translation table from ANSI escape sequences to color. Override
104 # this to specify your colors.
105 # this to specify your colors.
105 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
106 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
107 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
108 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
109 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
110 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
111 '1;34': [12, 'LIGHT BLUE'], '1;35':
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
112 [13, 'MEDIUM VIOLET RED'],
113 [13, 'MEDIUM VIOLET RED'],
113 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
114
115
115 # The color of the carret (call _apply_style() after setting)
116 # The color of the carret (call _apply_style() after setting)
116 carret_color = 'BLACK'
117 carret_color = 'BLACK'
117
118
119 # Store the last time a refresh was done
120 _last_refresh_time = 0
121
118 #--------------------------------------------------------------------------
122 #--------------------------------------------------------------------------
119 # Public API
123 # Public API
120 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
121
125
122 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
123 size=wx.DefaultSize, style=0, ):
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
124 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
125 self._configure_scintilla()
129 self._configure_scintilla()
126
130
127 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
128 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
129
133
130
134
131 def write(self, text, refresh=True):
135 def write(self, text, refresh=True):
132 """ Write given text to buffer, while translating the ansi escape
136 """ Write given text to buffer, while translating the ansi escape
133 sequences.
137 sequences.
134 """
138 """
135 # XXX: do not put print statements to sys.stdout/sys.stderr in
139 # XXX: do not put print statements to sys.stdout/sys.stderr in
136 # this method, the print statements will call this method, as
140 # this method, the print statements will call this method, as
137 # you will end up with an infinit loop
141 # you will end up with an infinit loop
138 title = self.title_pat.split(text)
142 title = self.title_pat.split(text)
139 if len(title)>1:
143 if len(title)>1:
140 self.title = title[-2]
144 self.title = title[-2]
141
145
142 text = self.title_pat.sub('', text)
146 text = self.title_pat.sub('', text)
143 segments = self.color_pat.split(text)
147 segments = self.color_pat.split(text)
144 segment = segments.pop(0)
148 segment = segments.pop(0)
145 self.GotoPos(self.GetLength())
149 self.GotoPos(self.GetLength())
146 self.StartStyling(self.GetLength(), 0xFF)
150 self.StartStyling(self.GetLength(), 0xFF)
147 try:
151 try:
148 self.AppendText(segment)
152 self.AppendText(segment)
149 except UnicodeDecodeError:
153 except UnicodeDecodeError:
150 # XXX: Do I really want to skip the exception?
154 # XXX: Do I really want to skip the exception?
151 pass
155 pass
152
156
153 if segments:
157 if segments:
154 for ansi_tag, text in zip(segments[::2], segments[1::2]):
158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
155 self.StartStyling(self.GetLength(), 0xFF)
159 self.StartStyling(self.GetLength(), 0xFF)
156 try:
160 try:
157 self.AppendText(text)
161 self.AppendText(text)
158 except UnicodeDecodeError:
162 except UnicodeDecodeError:
159 # XXX: Do I really want to skip the exception?
163 # XXX: Do I really want to skip the exception?
160 pass
164 pass
161
165
162 if ansi_tag not in self.ANSI_STYLES:
166 if ansi_tag not in self.ANSI_STYLES:
163 style = 0
167 style = 0
164 else:
168 else:
165 style = self.ANSI_STYLES[ansi_tag][0]
169 style = self.ANSI_STYLES[ansi_tag][0]
166
170
167 self.SetStyling(len(text), style)
171 self.SetStyling(len(text), style)
168
172
169 self.GotoPos(self.GetLength())
173 self.GotoPos(self.GetLength())
170 if refresh:
174 if refresh:
171 # Maybe this is faster than wx.Yield()
175 current_time = time.time()
172 self.ProcessEvent(wx.PaintEvent())
176 if current_time - self._last_refresh_time > 0.03:
173 #wx.Yield()
177 if sys.platform == 'win32':
178 wx.SafeYield()
179 else:
180 wx.Yield()
181 # self.ProcessEvent(wx.PaintEvent())
182 self._last_refresh_time = current_time
174
183
175
184
176 def new_prompt(self, prompt):
185 def new_prompt(self, prompt):
177 """ Prints a prompt at start of line, and move the start of the
186 """ Prints a prompt at start of line, and move the start of the
178 current block there.
187 current block there.
179
188
180 The prompt can be given with ascii escape sequences.
189 The prompt can be given with ascii escape sequences.
181 """
190 """
182 self.write(prompt, refresh=False)
191 self.write(prompt, refresh=False)
183 # now we update our cursor giving end of prompt
192 # now we update our cursor giving end of prompt
184 self.current_prompt_pos = self.GetLength()
193 self.current_prompt_pos = self.GetLength()
185 self.current_prompt_line = self.GetCurrentLine()
194 self.current_prompt_line = self.GetCurrentLine()
186 wx.Yield()
187 self.EnsureCaretVisible()
195 self.EnsureCaretVisible()
188
196
189
197
190 def scroll_to_bottom(self):
198 def scroll_to_bottom(self):
191 maxrange = self.GetScrollRange(wx.VERTICAL)
199 maxrange = self.GetScrollRange(wx.VERTICAL)
192 self.ScrollLines(maxrange)
200 self.ScrollLines(maxrange)
193
201
194
202
195 def pop_completion(self, possibilities, offset=0):
203 def pop_completion(self, possibilities, offset=0):
196 """ Pops up an autocompletion menu. Offset is the offset
204 """ Pops up an autocompletion menu. Offset is the offset
197 in characters of the position at which the menu should
205 in characters of the position at which the menu should
198 appear, relativ to the cursor.
206 appear, relativ to the cursor.
199 """
207 """
200 self.AutoCompSetIgnoreCase(False)
208 self.AutoCompSetIgnoreCase(False)
201 self.AutoCompSetAutoHide(False)
209 self.AutoCompSetAutoHide(False)
202 self.AutoCompSetMaxHeight(len(possibilities))
210 self.AutoCompSetMaxHeight(len(possibilities))
203 self.AutoCompShow(offset, " ".join(possibilities))
211 self.AutoCompShow(offset, " ".join(possibilities))
204
212
205
213
206 def get_line_width(self):
214 def get_line_width(self):
207 """ Return the width of the line in characters.
215 """ Return the width of the line in characters.
208 """
216 """
209 return self.GetSize()[0]/self.GetCharWidth()
217 return self.GetSize()[0]/self.GetCharWidth()
210
218
211 #--------------------------------------------------------------------------
219 #--------------------------------------------------------------------------
212 # EditWindow API
220 # EditWindow API
213 #--------------------------------------------------------------------------
221 #--------------------------------------------------------------------------
214
222
215 def OnUpdateUI(self, event):
223 def OnUpdateUI(self, event):
216 """ Override the OnUpdateUI of the EditWindow class, to prevent
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
217 syntax highlighting both for faster redraw, and for more
225 syntax highlighting both for faster redraw, and for more
218 consistent look and feel.
226 consistent look and feel.
219 """
227 """
220
228
221 #--------------------------------------------------------------------------
229 #--------------------------------------------------------------------------
222 # Private API
230 # Private API
223 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
224
232
225 def _apply_style(self):
233 def _apply_style(self):
226 """ Applies the colors for the different text elements and the
234 """ Applies the colors for the different text elements and the
227 carret.
235 carret.
228 """
236 """
229 self.SetCaretForeground(self.carret_color)
237 self.SetCaretForeground(self.carret_color)
230
238
231 #self.StyleClearAll()
239 #self.StyleClearAll()
232 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
233 "fore:#FF0000,back:#0000FF,bold")
241 "fore:#FF0000,back:#0000FF,bold")
234 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
235 "fore:#000000,back:#FF0000,bold")
243 "fore:#000000,back:#FF0000,bold")
236
244
237 for style in self.ANSI_STYLES.values():
245 for style in self.ANSI_STYLES.values():
238 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
239
247
240
248
241 def _configure_scintilla(self):
249 def _configure_scintilla(self):
242 self.SetEOLMode(stc.STC_EOL_LF)
250 self.SetEOLMode(stc.STC_EOL_LF)
243
251
244 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
245 # the widget
253 # the widget
246 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
254 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
247 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
255 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
248 # Also allow Ctrl Shift "=" for poor non US keyboard users.
256 # Also allow Ctrl Shift "=" for poor non US keyboard users.
249 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
257 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
250 stc.STC_CMD_ZOOMIN)
258 stc.STC_CMD_ZOOMIN)
251
259
252 # Keys: we need to clear some of the keys the that don't play
260 # Keys: we need to clear some of the keys the that don't play
253 # well with a console.
261 # well with a console.
254 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
262 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
255 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
263 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
256 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
264 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
257 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
265 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
258
266
259 self.SetEOLMode(stc.STC_EOL_CRLF)
267 self.SetEOLMode(stc.STC_EOL_CRLF)
260 self.SetWrapMode(stc.STC_WRAP_CHAR)
268 self.SetWrapMode(stc.STC_WRAP_CHAR)
261 self.SetWrapMode(stc.STC_WRAP_WORD)
269 self.SetWrapMode(stc.STC_WRAP_WORD)
262 self.SetBufferedDraw(True)
270 self.SetBufferedDraw(True)
263 self.SetUseAntiAliasing(True)
271 self.SetUseAntiAliasing(True)
264 self.SetLayoutCache(stc.STC_CACHE_PAGE)
272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
265 self.SetUndoCollection(False)
273 self.SetUndoCollection(False)
266 self.SetUseTabs(True)
274 self.SetUseTabs(True)
267 self.SetIndent(4)
275 self.SetIndent(4)
268 self.SetTabWidth(4)
276 self.SetTabWidth(4)
269
277
270 # we don't want scintilla's autocompletion to choose
278 # we don't want scintilla's autocompletion to choose
271 # automaticaly out of a single choice list, as we pop it up
279 # automaticaly out of a single choice list, as we pop it up
272 # automaticaly
280 # automaticaly
273 self.AutoCompSetChooseSingle(False)
281 self.AutoCompSetChooseSingle(False)
274 self.AutoCompSetMaxHeight(10)
282 self.AutoCompSetMaxHeight(10)
275 # XXX: this doesn't seem to have an effect.
283 # XXX: this doesn't seem to have an effect.
276 self.AutoCompSetFillUps('\n')
284 self.AutoCompSetFillUps('\n')
277
285
278 self.SetMargins(3, 3) #text is moved away from border with 3px
286 self.SetMargins(3, 3) #text is moved away from border with 3px
279 # Suppressing Scintilla margins
287 # Suppressing Scintilla margins
280 self.SetMarginWidth(0, 0)
288 self.SetMarginWidth(0, 0)
281 self.SetMarginWidth(1, 0)
289 self.SetMarginWidth(1, 0)
282 self.SetMarginWidth(2, 0)
290 self.SetMarginWidth(2, 0)
283
291
284 self._apply_style()
292 self._apply_style()
285
293
286 # Xterm escape sequences
294 # Xterm escape sequences
287 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
288 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
289
297
290 #self.SetEdgeMode(stc.STC_EDGE_LINE)
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
291 #self.SetEdgeColumn(80)
299 #self.SetEdgeColumn(80)
292
300
293 # styles
301 # styles
294 p = self.style
302 p = self.style
295 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
296 self.StyleClearAll()
304 self.StyleClearAll()
297 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
298 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
299 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
300
308
301 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
302 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
303 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
304 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
312 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
305 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
313 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
306 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
314 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
307 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
315 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
308 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
316 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
309 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
317 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
310 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
311 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
319 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
312 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
320 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
313 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
314 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
315
323
316 def _on_key_down(self, event, skip=True):
324 def _on_key_down(self, event, skip=True):
317 """ Key press callback used for correcting behavior for
325 """ Key press callback used for correcting behavior for
318 console-like interfaces: the cursor is constraint to be after
326 console-like interfaces: the cursor is constraint to be after
319 the last prompt.
327 the last prompt.
320
328
321 Return True if event as been catched.
329 Return True if event as been catched.
322 """
330 """
323 catched = True
331 catched = True
324 # Intercept some specific keys.
332 # Intercept some specific keys.
325 if event.KeyCode == ord('L') and event.ControlDown() :
333 if event.KeyCode == ord('L') and event.ControlDown() :
326 self.scroll_to_bottom()
334 self.scroll_to_bottom()
327 elif event.KeyCode == ord('K') and event.ControlDown() :
335 elif event.KeyCode == ord('K') and event.ControlDown() :
328 self.input_buffer = ''
336 self.input_buffer = ''
329 elif event.KeyCode == ord('A') and event.ControlDown() :
337 elif event.KeyCode == ord('A') and event.ControlDown() :
330 self.GotoPos(self.GetLength())
338 self.GotoPos(self.GetLength())
331 self.SetSelectionStart(self.current_prompt_pos)
339 self.SetSelectionStart(self.current_prompt_pos)
332 self.SetSelectionEnd(self.GetCurrentPos())
340 self.SetSelectionEnd(self.GetCurrentPos())
333 catched = True
341 catched = True
334 elif event.KeyCode == ord('E') and event.ControlDown() :
342 elif event.KeyCode == ord('E') and event.ControlDown() :
335 self.GotoPos(self.GetLength())
343 self.GotoPos(self.GetLength())
336 catched = True
344 catched = True
337 elif event.KeyCode == wx.WXK_PAGEUP:
345 elif event.KeyCode == wx.WXK_PAGEUP:
338 self.ScrollPages(-1)
346 self.ScrollPages(-1)
339 elif event.KeyCode == wx.WXK_PAGEDOWN:
347 elif event.KeyCode == wx.WXK_PAGEDOWN:
340 self.ScrollPages(1)
348 self.ScrollPages(1)
341 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
342 self.ScrollLines(-1)
350 self.ScrollLines(-1)
343 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
344 self.ScrollLines(1)
352 self.ScrollLines(1)
345 else:
353 else:
346 catched = False
354 catched = False
347
355
348 if self.AutoCompActive():
356 if self.AutoCompActive():
349 event.Skip()
357 event.Skip()
350 else:
358 else:
351 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
352 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
353 catched = True
361 catched = True
354 self.CallTipCancel()
362 self.CallTipCancel()
355 self.write('\n', refresh=False)
363 self.write('\n', refresh=False)
356 # Under windows scintilla seems to be doing funny stuff to the
364 # Under windows scintilla seems to be doing funny stuff to the
357 # line returns here, but the getter for input_buffer filters
365 # line returns here, but the getter for input_buffer filters
358 # this out.
366 # this out.
359 if sys.platform == 'win32':
367 if sys.platform == 'win32':
360 self.input_buffer = self.input_buffer
368 self.input_buffer = self.input_buffer
361 self._on_enter()
369 self._on_enter()
362
370
363 elif event.KeyCode == wx.WXK_HOME:
371 elif event.KeyCode == wx.WXK_HOME:
364 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
365 self.GotoPos(self.current_prompt_pos)
373 self.GotoPos(self.current_prompt_pos)
366 catched = True
374 catched = True
367
375
368 elif event.Modifiers == wx.MOD_SHIFT:
376 elif event.Modifiers == wx.MOD_SHIFT:
369 # FIXME: This behavior is not ideal: if the selection
377 # FIXME: This behavior is not ideal: if the selection
370 # is already started, it will jump.
378 # is already started, it will jump.
371 self.SetSelectionStart(self.current_prompt_pos)
379 self.SetSelectionStart(self.current_prompt_pos)
372 self.SetSelectionEnd(self.GetCurrentPos())
380 self.SetSelectionEnd(self.GetCurrentPos())
373 catched = True
381 catched = True
374
382
375 elif event.KeyCode == wx.WXK_UP:
383 elif event.KeyCode == wx.WXK_UP:
376 if self.GetCurrentLine() > self.current_prompt_line:
384 if self.GetCurrentLine() > self.current_prompt_line:
377 if self.GetCurrentLine() == self.current_prompt_line + 1 \
385 if self.GetCurrentLine() == self.current_prompt_line + 1 \
378 and self.GetColumn(self.GetCurrentPos()) < \
386 and self.GetColumn(self.GetCurrentPos()) < \
379 self.GetColumn(self.current_prompt_pos):
387 self.GetColumn(self.current_prompt_pos):
380 self.GotoPos(self.current_prompt_pos)
388 self.GotoPos(self.current_prompt_pos)
381 else:
389 else:
382 event.Skip()
390 event.Skip()
383 catched = True
391 catched = True
384
392
385 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
386 if self.GetCurrentPos() > self.current_prompt_pos:
394 if self.GetCurrentPos() > self.current_prompt_pos:
387 event.Skip()
395 event.Skip()
388 catched = True
396 catched = True
389
397
390 if skip and not catched:
398 if skip and not catched:
391 # Put the cursor back in the edit region
399 # Put the cursor back in the edit region
392 if self.GetCurrentPos() < self.current_prompt_pos:
400 if self.GetCurrentPos() < self.current_prompt_pos:
393 self.GotoPos(self.current_prompt_pos)
401 self.GotoPos(self.current_prompt_pos)
394 else:
402 else:
395 event.Skip()
403 event.Skip()
396
404
397 return catched
405 return catched
398
406
399
407
400 def _on_key_up(self, event, skip=True):
408 def _on_key_up(self, event, skip=True):
401 """ If cursor is outside the editing region, put it back.
409 """ If cursor is outside the editing region, put it back.
402 """
410 """
403 event.Skip()
411 event.Skip()
404 if self.GetCurrentPos() < self.current_prompt_pos:
412 if self.GetCurrentPos() < self.current_prompt_pos:
405 self.GotoPos(self.current_prompt_pos)
413 self.GotoPos(self.current_prompt_pos)
406
414
407
415
408
416
409 if __name__ == '__main__':
417 if __name__ == '__main__':
410 # Some simple code to test the console widget.
418 # Some simple code to test the console widget.
411 class MainWindow(wx.Frame):
419 class MainWindow(wx.Frame):
412 def __init__(self, parent, id, title):
420 def __init__(self, parent, id, title):
413 wx.Frame.__init__(self, parent, id, title, size=(300,250))
421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
414 self._sizer = wx.BoxSizer(wx.VERTICAL)
422 self._sizer = wx.BoxSizer(wx.VERTICAL)
415 self.console_widget = ConsoleWidget(self)
423 self.console_widget = ConsoleWidget(self)
416 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
417 self.SetSizer(self._sizer)
425 self.SetSizer(self._sizer)
418 self.SetAutoLayout(1)
426 self.SetAutoLayout(1)
419 self.Show(True)
427 self.Show(True)
420
428
421 app = wx.PySimpleApp()
429 app = wx.PySimpleApp()
422 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
430 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
423 w.SetSize((780, 460))
431 w.SetSize((780, 460))
424 w.Show()
432 w.Show()
425
433
426 app.MainLoop()
434 app.MainLoop()
427
435
428
436
@@ -1,510 +1,526 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 This class inherits from ConsoleWidget, that provides a console-like
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
10 """
11
11
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24
24
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 from time import sleep
28 from time import sleep
29 import sys
29 import sys
30 from threading import Lock
30 from threading import Lock
31 import string
31 import string
32
32
33 import wx
33 import wx
34 from wx import stc
34 from wx import stc
35
35
36 # Ipython-specific imports.
36 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
37 from IPython.frontend._process import PipedProcess
38 from console_widget import ConsoleWidget
38 from console_widget import ConsoleWidget
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
40
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42 # Constants
42 # Constants
43 #-------------------------------------------------------------------------------
43 #-------------------------------------------------------------------------------
44
44
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
47 _ERROR_BG = '#FFF1F1' # Nice red
48
48
49 _COMPLETE_BUFFER_MARKER = 31
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
51 _INPUT_MARKER = 29
52
52
53 prompt_in1 = \
53 prompt_in1 = \
54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55
55
56 prompt_out = \
56 prompt_out = \
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58
58
59 #-------------------------------------------------------------------------------
59 #-------------------------------------------------------------------------------
60 # Classes to implement the Wx frontend
60 # Classes to implement the Wx frontend
61 #-------------------------------------------------------------------------------
61 #-------------------------------------------------------------------------------
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 """Classes to provide a Wx frontend to the
63 """Classes to provide a Wx frontend to the
64 IPython.kernel.core.interpreter.
64 IPython.kernel.core.interpreter.
65
65
66 This class inherits from ConsoleWidget, that provides a console-like
66 This class inherits from ConsoleWidget, that provides a console-like
67 widget to provide a text-rendering widget suitable for a terminal.
67 widget to provide a text-rendering widget suitable for a terminal.
68 """
68 """
69
69
70 output_prompt_template = string.Template(prompt_out)
70 output_prompt_template = string.Template(prompt_out)
71
71
72 input_prompt_template = string.Template(prompt_in1)
72 input_prompt_template = string.Template(prompt_in1)
73
73
74 # Print debug info on what is happening to the console.
74 # Print debug info on what is happening to the console.
75 debug = False
75 debug = False
76
76
77 # The title of the terminal, as captured through the ANSI escape
77 # The title of the terminal, as captured through the ANSI escape
78 # sequences.
78 # sequences.
79 def _set_title(self, title):
79 def _set_title(self, title):
80 return self.Parent.SetTitle(title)
80 return self.Parent.SetTitle(title)
81
81
82 def _get_title(self):
82 def _get_title(self):
83 return self.Parent.GetTitle()
83 return self.Parent.GetTitle()
84
84
85 title = property(_get_title, _set_title)
85 title = property(_get_title, _set_title)
86
86
87
87
88 # The buffer being edited.
88 # The buffer being edited.
89 # We are duplicating the definition here because of multiple
89 # We are duplicating the definition here because of multiple
90 # inheritence
90 # inheritence
91 def _set_input_buffer(self, string):
91 def _set_input_buffer(self, string):
92 ConsoleWidget._set_input_buffer(self, string)
92 ConsoleWidget._set_input_buffer(self, string)
93 self._colorize_input_buffer()
93 self._colorize_input_buffer()
94
94
95 def _get_input_buffer(self):
95 def _get_input_buffer(self):
96 """ Returns the text in current edit buffer.
96 """ Returns the text in current edit buffer.
97 """
97 """
98 return ConsoleWidget._get_input_buffer(self)
98 return ConsoleWidget._get_input_buffer(self)
99
99
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101
101
102
102
103 #--------------------------------------------------------------------------
103 #--------------------------------------------------------------------------
104 # Private Attributes
104 # Private Attributes
105 #--------------------------------------------------------------------------
105 #--------------------------------------------------------------------------
106
106
107 # A flag governing the behavior of the input. Can be:
107 # A flag governing the behavior of the input. Can be:
108 #
108 #
109 # 'readline' for readline-like behavior with a prompt
109 # 'readline' for readline-like behavior with a prompt
110 # and an edit buffer.
110 # and an edit buffer.
111 # 'raw_input' similar to readline, but triggered by a raw-input
111 # 'raw_input' similar to readline, but triggered by a raw-input
112 # call. Can be used by subclasses to act differently.
112 # call. Can be used by subclasses to act differently.
113 # 'subprocess' for sending the raw input directly to a
113 # 'subprocess' for sending the raw input directly to a
114 # subprocess.
114 # subprocess.
115 # 'buffering' for buffering of the input, that will be used
115 # 'buffering' for buffering of the input, that will be used
116 # when the input state switches back to another state.
116 # when the input state switches back to another state.
117 _input_state = 'readline'
117 _input_state = 'readline'
118
118
119 # Attribute to store reference to the pipes of a subprocess, if we
119 # Attribute to store reference to the pipes of a subprocess, if we
120 # are running any.
120 # are running any.
121 _running_process = False
121 _running_process = False
122
122
123 # A queue for writing fast streams to the screen without flooding the
123 # A queue for writing fast streams to the screen without flooding the
124 # event loop
124 # event loop
125 _out_buffer = []
125 _out_buffer = []
126
126
127 # A lock to lock the _out_buffer to make sure we don't empty it
127 # A lock to lock the _out_buffer to make sure we don't empty it
128 # while it is being swapped
128 # while it is being swapped
129 _out_buffer_lock = Lock()
129 _out_buffer_lock = Lock()
130
130
131 # The different line markers used to higlight the prompts.
131 _markers = dict()
132 _markers = dict()
132
133
133 #--------------------------------------------------------------------------
134 #--------------------------------------------------------------------------
134 # Public API
135 # Public API
135 #--------------------------------------------------------------------------
136 #--------------------------------------------------------------------------
136
137
137 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
138 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
139 size=wx.DefaultSize,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
139 *args, **kwds):
141 *args, **kwds):
140 """ Create Shell instance.
142 """ Create Shell instance.
141 """
143 """
142 ConsoleWidget.__init__(self, parent, id, pos, size, style)
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
143 PrefilterFrontEnd.__init__(self, **kwds)
145 PrefilterFrontEnd.__init__(self, **kwds)
146
147 # Stick in our own raw_input:
148 self.ipython0.raw_input = self.raw_input
144
149
145 # Marker for complete buffer.
150 # Marker for complete buffer.
146 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
151 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
147 background=_COMPLETE_BUFFER_BG)
152 background=_COMPLETE_BUFFER_BG)
148 # Marker for current input buffer.
153 # Marker for current input buffer.
149 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
154 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
150 background=_INPUT_BUFFER_BG)
155 background=_INPUT_BUFFER_BG)
151 # Marker for tracebacks.
156 # Marker for tracebacks.
152 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
157 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
153 background=_ERROR_BG)
158 background=_ERROR_BG)
154
159
155 # A time for flushing the write buffer
160 # A time for flushing the write buffer
156 BUFFER_FLUSH_TIMER_ID = 100
161 BUFFER_FLUSH_TIMER_ID = 100
157 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
162 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
158 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
163 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
159
164
160 if 'debug' in kwds:
165 if 'debug' in kwds:
161 self.debug = kwds['debug']
166 self.debug = kwds['debug']
162 kwds.pop('debug')
167 kwds.pop('debug')
163
168
164 # Inject self in namespace, for debug
169 # Inject self in namespace, for debug
165 if self.debug:
170 if self.debug:
166 self.shell.user_ns['self'] = self
171 self.shell.user_ns['self'] = self
172 # Inject our own raw_input in namespace
173 self.shell.user_ns['raw_input'] = self.raw_input
167
174
168
175
169 def raw_input(self, prompt):
176 def raw_input(self, prompt=''):
170 """ A replacement from python's raw_input.
177 """ A replacement from python's raw_input.
171 """
178 """
172 self.new_prompt(prompt)
179 self.new_prompt(prompt)
173 self._input_state = 'raw_input'
180 self._input_state = 'raw_input'
174 if hasattr(self, '_cursor'):
181 if hasattr(self, '_cursor'):
175 del self._cursor
182 del self._cursor
176 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
183 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
177 self.waiting = True
178 self.__old_on_enter = self._on_enter
184 self.__old_on_enter = self._on_enter
185 event_loop = wx.EventLoop()
179 def my_on_enter():
186 def my_on_enter():
180 self.waiting = False
187 event_loop.Exit()
181 self._on_enter = my_on_enter
188 self._on_enter = my_on_enter
182 # XXX: Busy waiting, ugly.
189 # XXX: Running a separate event_loop. Ugly.
183 while self.waiting:
190 event_loop.Run()
184 wx.Yield()
185 sleep(0.1)
186 self._on_enter = self.__old_on_enter
191 self._on_enter = self.__old_on_enter
187 self._input_state = 'buffering'
192 self._input_state = 'buffering'
188 self._cursor = wx.BusyCursor()
193 self._cursor = wx.BusyCursor()
189 return self.input_buffer.rstrip('\n')
194 return self.input_buffer.rstrip('\n')
190
195
191
196
192 def system_call(self, command_string):
197 def system_call(self, command_string):
193 self._input_state = 'subprocess'
198 self._input_state = 'subprocess'
199 event_loop = wx.EventLoop()
200 def _end_system_call():
201 self._input_state = 'buffering'
202 self._running_process = False
203 event_loop.Exit()
204
194 self._running_process = PipedProcess(command_string,
205 self._running_process = PipedProcess(command_string,
195 out_callback=self.buffered_write,
206 out_callback=self.buffered_write,
196 end_callback = self._end_system_call)
207 end_callback = _end_system_call)
197 self._running_process.start()
208 self._running_process.start()
198 # XXX: another one of these polling loops to have a blocking
209 # XXX: Running a separate event_loop. Ugly.
199 # call
210 event_loop.Run()
200 wx.Yield()
201 while self._running_process:
202 wx.Yield()
203 sleep(0.1)
204 # Be sure to flush the buffer.
211 # Be sure to flush the buffer.
205 self._buffer_flush(event=None)
212 self._buffer_flush(event=None)
206
213
207
214
208 def do_calltip(self):
215 def do_calltip(self):
209 """ Analyse current and displays useful calltip for it.
216 """ Analyse current and displays useful calltip for it.
210 """
217 """
211 if self.debug:
218 if self.debug:
212 print >>sys.__stdout__, "do_calltip"
219 print >>sys.__stdout__, "do_calltip"
213 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
220 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
214 symbol = self.input_buffer
221 symbol = self.input_buffer
215 symbol_string = separators.split(symbol)[-1]
222 symbol_string = separators.split(symbol)[-1]
216 base_symbol_string = symbol_string.split('.')[0]
223 base_symbol_string = symbol_string.split('.')[0]
217 if base_symbol_string in self.shell.user_ns:
224 if base_symbol_string in self.shell.user_ns:
218 symbol = self.shell.user_ns[base_symbol_string]
225 symbol = self.shell.user_ns[base_symbol_string]
219 elif base_symbol_string in self.shell.user_global_ns:
226 elif base_symbol_string in self.shell.user_global_ns:
220 symbol = self.shell.user_global_ns[base_symbol_string]
227 symbol = self.shell.user_global_ns[base_symbol_string]
221 elif base_symbol_string in __builtin__.__dict__:
228 elif base_symbol_string in __builtin__.__dict__:
222 symbol = __builtin__.__dict__[base_symbol_string]
229 symbol = __builtin__.__dict__[base_symbol_string]
223 else:
230 else:
224 return False
231 return False
225 try:
232 try:
226 for name in symbol_string.split('.')[1:] + ['__doc__']:
233 for name in symbol_string.split('.')[1:] + ['__doc__']:
227 symbol = getattr(symbol, name)
234 symbol = getattr(symbol, name)
228 self.AutoCompCancel()
235 self.AutoCompCancel()
229 wx.Yield()
236 # Check that the symbol can indeed be converted to a string:
230 self.CallTipShow(self.GetCurrentPos(), symbol)
237 symbol += ''
238 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
231 except:
239 except:
232 # The retrieve symbol couldn't be converted to a string
240 # The retrieve symbol couldn't be converted to a string
233 pass
241 pass
234
242
235
243
236 def _popup_completion(self, create=False):
244 def _popup_completion(self, create=False):
237 """ Updates the popup completion menu if it exists. If create is
245 """ Updates the popup completion menu if it exists. If create is
238 true, open the menu.
246 true, open the menu.
239 """
247 """
240 if self.debug:
248 if self.debug:
241 print >>sys.__stdout__, "_popup_completion",
249 print >>sys.__stdout__, "_popup_completion"
242 line = self.input_buffer
250 line = self.input_buffer
243 if (self.AutoCompActive() and not line[-1] == '.') \
251 if (self.AutoCompActive() and line and not line[-1] == '.') \
244 or create==True:
252 or create==True:
245 suggestion, completions = self.complete(line)
253 suggestion, completions = self.complete(line)
246 offset=0
254 offset=0
247 if completions:
255 if completions:
248 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
256 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
249 residual = complete_sep.split(line)[-1]
257 residual = complete_sep.split(line)[-1]
250 offset = len(residual)
258 offset = len(residual)
251 self.pop_completion(completions, offset=offset)
259 self.pop_completion(completions, offset=offset)
252 if self.debug:
260 if self.debug:
253 print >>sys.__stdout__, completions
261 print >>sys.__stdout__, completions
254
262
255
263
256 def buffered_write(self, text):
264 def buffered_write(self, text):
257 """ A write method for streams, that caches the stream in order
265 """ A write method for streams, that caches the stream in order
258 to avoid flooding the event loop.
266 to avoid flooding the event loop.
259
267
260 This can be called outside of the main loop, in separate
268 This can be called outside of the main loop, in separate
261 threads.
269 threads.
262 """
270 """
263 self._out_buffer_lock.acquire()
271 self._out_buffer_lock.acquire()
264 self._out_buffer.append(text)
272 self._out_buffer.append(text)
265 self._out_buffer_lock.release()
273 self._out_buffer_lock.release()
266 if not self._buffer_flush_timer.IsRunning():
274 if not self._buffer_flush_timer.IsRunning():
267 wx.CallAfter(self._buffer_flush_timer.Start,
275 wx.CallAfter(self._buffer_flush_timer.Start,
268 milliseconds=100, oneShot=True)
276 milliseconds=100, oneShot=True)
269
277
270
278
271 #--------------------------------------------------------------------------
279 #--------------------------------------------------------------------------
272 # LineFrontEnd interface
280 # LineFrontEnd interface
273 #--------------------------------------------------------------------------
281 #--------------------------------------------------------------------------
274
282
275 def execute(self, python_string, raw_string=None):
283 def execute(self, python_string, raw_string=None):
276 self._input_state = 'buffering'
284 self._input_state = 'buffering'
277 self.CallTipCancel()
285 self.CallTipCancel()
278 self._cursor = wx.BusyCursor()
286 self._cursor = wx.BusyCursor()
279 if raw_string is None:
287 if raw_string is None:
280 raw_string = python_string
288 raw_string = python_string
281 end_line = self.current_prompt_line \
289 end_line = self.current_prompt_line \
282 + max(1, len(raw_string.split('\n'))-1)
290 + max(1, len(raw_string.split('\n'))-1)
283 for i in range(self.current_prompt_line, end_line):
291 for i in range(self.current_prompt_line, end_line):
284 if i in self._markers:
292 if i in self._markers:
285 self.MarkerDeleteHandle(self._markers[i])
293 self.MarkerDeleteHandle(self._markers[i])
286 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
294 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
287 # Update the display:
295 # Use a callafter to update the display robustly under windows
288 wx.Yield()
296 def callback():
289 self.GotoPos(self.GetLength())
297 self.GotoPos(self.GetLength())
290 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
298 PrefilterFrontEnd.execute(self, python_string,
299 raw_string=raw_string)
300 wx.CallAfter(callback)
291
301
292 def save_output_hooks(self):
302 def save_output_hooks(self):
293 self.__old_raw_input = __builtin__.raw_input
303 self.__old_raw_input = __builtin__.raw_input
294 PrefilterFrontEnd.save_output_hooks(self)
304 PrefilterFrontEnd.save_output_hooks(self)
295
305
296 def capture_output(self):
306 def capture_output(self):
297 __builtin__.raw_input = self.raw_input
298 self.SetLexer(stc.STC_LEX_NULL)
307 self.SetLexer(stc.STC_LEX_NULL)
299 PrefilterFrontEnd.capture_output(self)
308 PrefilterFrontEnd.capture_output(self)
309 __builtin__.raw_input = self.raw_input
300
310
301
311
302 def release_output(self):
312 def release_output(self):
303 __builtin__.raw_input = self.__old_raw_input
313 __builtin__.raw_input = self.__old_raw_input
304 PrefilterFrontEnd.release_output(self)
314 PrefilterFrontEnd.release_output(self)
305 self.SetLexer(stc.STC_LEX_PYTHON)
315 self.SetLexer(stc.STC_LEX_PYTHON)
306
316
307
317
308 def after_execute(self):
318 def after_execute(self):
309 PrefilterFrontEnd.after_execute(self)
319 PrefilterFrontEnd.after_execute(self)
310 # Clear the wait cursor
320 # Clear the wait cursor
311 if hasattr(self, '_cursor'):
321 if hasattr(self, '_cursor'):
312 del self._cursor
322 del self._cursor
313 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
323 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
314
324
315
325
316 def show_traceback(self):
326 def show_traceback(self):
317 start_line = self.GetCurrentLine()
327 start_line = self.GetCurrentLine()
318 PrefilterFrontEnd.show_traceback(self)
328 PrefilterFrontEnd.show_traceback(self)
319 wx.Yield()
329 self.ProcessEvent(wx.PaintEvent())
330 #wx.Yield()
320 for i in range(start_line, self.GetCurrentLine()):
331 for i in range(start_line, self.GetCurrentLine()):
321 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
332 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
322
333
323
334
324 #--------------------------------------------------------------------------
335 #--------------------------------------------------------------------------
336 # FrontEndBase interface
337 #--------------------------------------------------------------------------
338
339 def render_error(self, e):
340 start_line = self.GetCurrentLine()
341 self.write('\n' + e + '\n')
342 for i in range(start_line, self.GetCurrentLine()):
343 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
344
345
346 #--------------------------------------------------------------------------
325 # ConsoleWidget interface
347 # ConsoleWidget interface
326 #--------------------------------------------------------------------------
348 #--------------------------------------------------------------------------
327
349
328 def new_prompt(self, prompt):
350 def new_prompt(self, prompt):
329 """ Display a new prompt, and start a new input buffer.
351 """ Display a new prompt, and start a new input buffer.
330 """
352 """
331 self._input_state = 'readline'
353 self._input_state = 'readline'
332 ConsoleWidget.new_prompt(self, prompt)
354 ConsoleWidget.new_prompt(self, prompt)
333 i = self.current_prompt_line
355 i = self.current_prompt_line
334 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
356 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
335
357
336
358
337 def write(self, *args, **kwargs):
359 def write(self, *args, **kwargs):
338 # Avoid multiple inheritence, be explicit about which
360 # Avoid multiple inheritence, be explicit about which
339 # parent method class gets called
361 # parent method class gets called
340 ConsoleWidget.write(self, *args, **kwargs)
362 ConsoleWidget.write(self, *args, **kwargs)
341
363
342
364
343 def _on_key_down(self, event, skip=True):
365 def _on_key_down(self, event, skip=True):
344 """ Capture the character events, let the parent
366 """ Capture the character events, let the parent
345 widget handle them, and put our logic afterward.
367 widget handle them, and put our logic afterward.
346 """
368 """
347 # FIXME: This method needs to be broken down in smaller ones.
369 # FIXME: This method needs to be broken down in smaller ones.
348 current_line_number = self.GetCurrentLine()
370 current_line_number = self.GetCurrentLine()
349 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
371 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
350 # Capture Control-C
372 # Capture Control-C
351 if self._input_state == 'subprocess':
373 if self._input_state == 'subprocess':
352 if self.debug:
374 if self.debug:
353 print >>sys.__stderr__, 'Killing running process'
375 print >>sys.__stderr__, 'Killing running process'
354 self._running_process.process.kill()
376 if hasattr(self._running_process, 'process'):
377 self._running_process.process.kill()
355 elif self._input_state == 'buffering':
378 elif self._input_state == 'buffering':
356 if self.debug:
379 if self.debug:
357 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
380 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
358 raise KeyboardInterrupt
381 raise KeyboardInterrupt
359 # XXX: We need to make really sure we
382 # XXX: We need to make really sure we
360 # get back to a prompt.
383 # get back to a prompt.
361 elif self._input_state == 'subprocess' and (
384 elif self._input_state == 'subprocess' and (
362 ( event.KeyCode<256 and
385 ( event.KeyCode<256 and
363 not event.ControlDown() )
386 not event.ControlDown() )
364 or
387 or
365 ( event.KeyCode in (ord('d'), ord('D')) and
388 ( event.KeyCode in (ord('d'), ord('D')) and
366 event.ControlDown())):
389 event.ControlDown())):
367 # We are running a process, we redirect keys.
390 # We are running a process, we redirect keys.
368 ConsoleWidget._on_key_down(self, event, skip=skip)
391 ConsoleWidget._on_key_down(self, event, skip=skip)
369 char = chr(event.KeyCode)
392 char = chr(event.KeyCode)
370 # Deal with some inconsistency in wx keycodes:
393 # Deal with some inconsistency in wx keycodes:
371 if char == '\r':
394 if char == '\r':
372 char = '\n'
395 char = '\n'
373 elif not event.ShiftDown():
396 elif not event.ShiftDown():
374 char = char.lower()
397 char = char.lower()
375 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
398 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
376 char = '\04'
399 char = '\04'
377 self._running_process.process.stdin.write(char)
400 self._running_process.process.stdin.write(char)
378 self._running_process.process.stdin.flush()
401 self._running_process.process.stdin.flush()
379 elif event.KeyCode in (ord('('), 57):
402 elif event.KeyCode in (ord('('), 57, 53):
380 # Calltips
403 # Calltips
381 event.Skip()
404 event.Skip()
382 self.do_calltip()
405 self.do_calltip()
383 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
406 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
384 event.Skip()
407 event.Skip()
385 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
408 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
386 wx.CallAfter(self._popup_completion, create=True)
409 wx.CallAfter(self._popup_completion, create=True)
387 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
410 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
388 wx.WXK_RIGHT, wx.WXK_ESCAPE):
411 wx.WXK_RIGHT, wx.WXK_ESCAPE):
389 wx.CallAfter(self._popup_completion)
412 wx.CallAfter(self._popup_completion)
390 else:
413 else:
391 # Up history
414 # Up history
392 if event.KeyCode == wx.WXK_UP and (
415 if event.KeyCode == wx.WXK_UP and (
393 ( current_line_number == self.current_prompt_line and
416 ( current_line_number == self.current_prompt_line and
394 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
417 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
395 or event.ControlDown() ):
418 or event.ControlDown() ):
396 new_buffer = self.get_history_previous(
419 new_buffer = self.get_history_previous(
397 self.input_buffer)
420 self.input_buffer)
398 if new_buffer is not None:
421 if new_buffer is not None:
399 self.input_buffer = new_buffer
422 self.input_buffer = new_buffer
400 if self.GetCurrentLine() > self.current_prompt_line:
423 if self.GetCurrentLine() > self.current_prompt_line:
401 # Go to first line, for seemless history up.
424 # Go to first line, for seemless history up.
402 self.GotoPos(self.current_prompt_pos)
425 self.GotoPos(self.current_prompt_pos)
403 # Down history
426 # Down history
404 elif event.KeyCode == wx.WXK_DOWN and (
427 elif event.KeyCode == wx.WXK_DOWN and (
405 ( current_line_number == self.LineCount -1 and
428 ( current_line_number == self.LineCount -1 and
406 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
429 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
407 or event.ControlDown() ):
430 or event.ControlDown() ):
408 new_buffer = self.get_history_next()
431 new_buffer = self.get_history_next()
409 if new_buffer is not None:
432 if new_buffer is not None:
410 self.input_buffer = new_buffer
433 self.input_buffer = new_buffer
411 # Tab-completion
434 # Tab-completion
412 elif event.KeyCode == ord('\t'):
435 elif event.KeyCode == ord('\t'):
413 last_line = self.input_buffer.split('\n')[-1]
436 current_line, current_line_number = self.CurLine
414 if not re.match(r'^\s*$', last_line):
437 if not re.match(r'^\s*$', current_line):
415 self.complete_current_input()
438 self.complete_current_input()
416 if self.AutoCompActive():
439 if self.AutoCompActive():
417 wx.CallAfter(self._popup_completion, create=True)
440 wx.CallAfter(self._popup_completion, create=True)
418 else:
441 else:
419 event.Skip()
442 event.Skip()
420 else:
443 else:
421 ConsoleWidget._on_key_down(self, event, skip=skip)
444 ConsoleWidget._on_key_down(self, event, skip=skip)
422
445
423
446
424 def _on_key_up(self, event, skip=True):
447 def _on_key_up(self, event, skip=True):
425 """ Called when any key is released.
448 """ Called when any key is released.
426 """
449 """
427 if event.KeyCode in (59, ord('.')):
450 if event.KeyCode in (59, ord('.')):
428 # Intercepting '.'
451 # Intercepting '.'
429 event.Skip()
452 event.Skip()
430 self._popup_completion(create=True)
453 wx.CallAfter(self._popup_completion, create=True)
431 else:
454 else:
432 ConsoleWidget._on_key_up(self, event, skip=skip)
455 ConsoleWidget._on_key_up(self, event, skip=skip)
433
456
434
457
435 def _on_enter(self):
458 def _on_enter(self):
436 """ Called on return key down, in readline input_state.
459 """ Called on return key down, in readline input_state.
437 """
460 """
438 if self.debug:
461 if self.debug:
439 print >>sys.__stdout__, repr(self.input_buffer)
462 print >>sys.__stdout__, repr(self.input_buffer)
440 PrefilterFrontEnd._on_enter(self)
463 PrefilterFrontEnd._on_enter(self)
441
464
442
465
443 #--------------------------------------------------------------------------
466 #--------------------------------------------------------------------------
444 # EditWindow API
467 # EditWindow API
445 #--------------------------------------------------------------------------
468 #--------------------------------------------------------------------------
446
469
447 def OnUpdateUI(self, event):
470 def OnUpdateUI(self, event):
448 """ Override the OnUpdateUI of the EditWindow class, to prevent
471 """ Override the OnUpdateUI of the EditWindow class, to prevent
449 syntax highlighting both for faster redraw, and for more
472 syntax highlighting both for faster redraw, and for more
450 consistent look and feel.
473 consistent look and feel.
451 """
474 """
452 if not self._input_state == 'readline':
475 if not self._input_state == 'readline':
453 ConsoleWidget.OnUpdateUI(self, event)
476 ConsoleWidget.OnUpdateUI(self, event)
454
477
455 #--------------------------------------------------------------------------
478 #--------------------------------------------------------------------------
456 # Private API
479 # Private API
457 #--------------------------------------------------------------------------
480 #--------------------------------------------------------------------------
458
481
459 def _end_system_call(self):
460 """ Called at the end of a system call.
461 """
462 self._input_state = 'buffering'
463 self._running_process = False
464
465
466 def _buffer_flush(self, event):
482 def _buffer_flush(self, event):
467 """ Called by the timer to flush the write buffer.
483 """ Called by the timer to flush the write buffer.
468
484
469 This is always called in the mainloop, by the wx timer.
485 This is always called in the mainloop, by the wx timer.
470 """
486 """
471 self._out_buffer_lock.acquire()
487 self._out_buffer_lock.acquire()
472 _out_buffer = self._out_buffer
488 _out_buffer = self._out_buffer
473 self._out_buffer = []
489 self._out_buffer = []
474 self._out_buffer_lock.release()
490 self._out_buffer_lock.release()
475 self.write(''.join(_out_buffer), refresh=False)
491 self.write(''.join(_out_buffer), refresh=False)
476
492
477
493
478 def _colorize_input_buffer(self):
494 def _colorize_input_buffer(self):
479 """ Keep the input buffer lines at a bright color.
495 """ Keep the input buffer lines at a bright color.
480 """
496 """
481 if not self._input_state in ('readline', 'raw_input'):
497 if not self._input_state in ('readline', 'raw_input'):
482 return
498 return
483 end_line = self.GetCurrentLine()
499 end_line = self.GetCurrentLine()
484 if not sys.platform == 'win32':
500 if not sys.platform == 'win32':
485 end_line += 1
501 end_line += 1
486 for i in range(self.current_prompt_line, end_line):
502 for i in range(self.current_prompt_line, end_line):
487 if i in self._markers:
503 if i in self._markers:
488 self.MarkerDeleteHandle(self._markers[i])
504 self.MarkerDeleteHandle(self._markers[i])
489 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
505 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
490
506
491
507
492 if __name__ == '__main__':
508 if __name__ == '__main__':
493 class MainWindow(wx.Frame):
509 class MainWindow(wx.Frame):
494 def __init__(self, parent, id, title):
510 def __init__(self, parent, id, title):
495 wx.Frame.__init__(self, parent, id, title, size=(300,250))
511 wx.Frame.__init__(self, parent, id, title, size=(300,250))
496 self._sizer = wx.BoxSizer(wx.VERTICAL)
512 self._sizer = wx.BoxSizer(wx.VERTICAL)
497 self.shell = WxController(self)
513 self.shell = WxController(self)
498 self._sizer.Add(self.shell, 1, wx.EXPAND)
514 self._sizer.Add(self.shell, 1, wx.EXPAND)
499 self.SetSizer(self._sizer)
515 self.SetSizer(self._sizer)
500 self.SetAutoLayout(1)
516 self.SetAutoLayout(1)
501 self.Show(True)
517 self.Show(True)
502
518
503 app = wx.PySimpleApp()
519 app = wx.PySimpleApp()
504 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
520 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
505 frame.shell.SetFocus()
521 frame.shell.SetFocus()
506 frame.SetSize((680, 460))
522 frame.SetSize((680, 460))
507 self = frame.shell
523 self = frame.shell
508
524
509 app.MainLoop()
525 app.MainLoop()
510
526
@@ -1,2132 +1,2164 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 General purpose utilities.
3 General purpose utilities.
4
4
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
6 these things are also convenient when working at the command line.
6 these things are also convenient when working at the command line.
7
7
8 $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $"""
8 $Id: genutils.py 2998 2008-01-31 10:06:04Z vivainio $"""
9
9
10 #*****************************************************************************
10 #*****************************************************************************
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #*****************************************************************************
15 #*****************************************************************************
16
16
17 from IPython import Release
17 from IPython import Release
18 __author__ = '%s <%s>' % Release.authors['Fernando']
18 __author__ = '%s <%s>' % Release.authors['Fernando']
19 __license__ = Release.license
19 __license__ = Release.license
20
20
21 #****************************************************************************
21 #****************************************************************************
22 # required modules from the Python standard library
22 # required modules from the Python standard library
23 import __main__
23 import __main__
24 import commands
24 import commands
25 try:
25 try:
26 import doctest
26 import doctest
27 except ImportError:
27 except ImportError:
28 pass
28 pass
29 import os
29 import os
30 import platform
30 import platform
31 import re
31 import re
32 import shlex
32 import shlex
33 import shutil
33 import shutil
34 import subprocess
34 import subprocess
35 import sys
35 import sys
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import types
38 import types
39 import warnings
39 import warnings
40
40
41 # Curses and termios are Unix-only modules
41 # Curses and termios are Unix-only modules
42 try:
42 try:
43 import curses
43 import curses
44 # We need termios as well, so if its import happens to raise, we bail on
44 # We need termios as well, so if its import happens to raise, we bail on
45 # using curses altogether.
45 # using curses altogether.
46 import termios
46 import termios
47 except ImportError:
47 except ImportError:
48 USE_CURSES = False
48 USE_CURSES = False
49 else:
49 else:
50 # Curses on Solaris may not be complete, so we can't use it there
50 # Curses on Solaris may not be complete, so we can't use it there
51 USE_CURSES = hasattr(curses,'initscr')
51 USE_CURSES = hasattr(curses,'initscr')
52
52
53 # Other IPython utilities
53 # Other IPython utilities
54 import IPython
54 import IPython
55 from IPython.Itpl import Itpl,itpl,printpl
55 from IPython.Itpl import Itpl,itpl,printpl
56 from IPython import DPyGetOpt, platutils
56 from IPython import DPyGetOpt, platutils
57 from IPython.generics import result_display
57 from IPython.generics import result_display
58 import IPython.ipapi
58 import IPython.ipapi
59 from IPython.external.path import path
59 from IPython.external.path import path
60 if os.name == "nt":
60 if os.name == "nt":
61 from IPython.winconsole import get_console_size
61 from IPython.winconsole import get_console_size
62
62
63 try:
63 try:
64 set
64 set
65 except:
65 except:
66 from sets import Set as set
66 from sets import Set as set
67
67
68
68
69 #****************************************************************************
69 #****************************************************************************
70 # Exceptions
70 # Exceptions
71 class Error(Exception):
71 class Error(Exception):
72 """Base class for exceptions in this module."""
72 """Base class for exceptions in this module."""
73 pass
73 pass
74
74
75 #----------------------------------------------------------------------------
75 #----------------------------------------------------------------------------
76 class IOStream:
76 class IOStream:
77 def __init__(self,stream,fallback):
77 def __init__(self,stream,fallback):
78 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
78 if not hasattr(stream,'write') or not hasattr(stream,'flush'):
79 stream = fallback
79 stream = fallback
80 self.stream = stream
80 self.stream = stream
81 self._swrite = stream.write
81 self._swrite = stream.write
82 self.flush = stream.flush
82 self.flush = stream.flush
83
83
84 def write(self,data):
84 def write(self,data):
85 try:
85 try:
86 self._swrite(data)
86 self._swrite(data)
87 except:
87 except:
88 try:
88 try:
89 # print handles some unicode issues which may trip a plain
89 # print handles some unicode issues which may trip a plain
90 # write() call. Attempt to emulate write() by using a
90 # write() call. Attempt to emulate write() by using a
91 # trailing comma
91 # trailing comma
92 print >> self.stream, data,
92 print >> self.stream, data,
93 except:
93 except:
94 # if we get here, something is seriously broken.
94 # if we get here, something is seriously broken.
95 print >> sys.stderr, \
95 print >> sys.stderr, \
96 'ERROR - failed to write data to stream:', self.stream
96 'ERROR - failed to write data to stream:', self.stream
97
97
98 def close(self):
98 def close(self):
99 pass
99 pass
100
100
101
101
102 class IOTerm:
102 class IOTerm:
103 """ Term holds the file or file-like objects for handling I/O operations.
103 """ Term holds the file or file-like objects for handling I/O operations.
104
104
105 These are normally just sys.stdin, sys.stdout and sys.stderr but for
105 These are normally just sys.stdin, sys.stdout and sys.stderr but for
106 Windows they can can replaced to allow editing the strings before they are
106 Windows they can can replaced to allow editing the strings before they are
107 displayed."""
107 displayed."""
108
108
109 # In the future, having IPython channel all its I/O operations through
109 # In the future, having IPython channel all its I/O operations through
110 # this class will make it easier to embed it into other environments which
110 # this class will make it easier to embed it into other environments which
111 # are not a normal terminal (such as a GUI-based shell)
111 # are not a normal terminal (such as a GUI-based shell)
112 def __init__(self,cin=None,cout=None,cerr=None):
112 def __init__(self,cin=None,cout=None,cerr=None):
113 self.cin = IOStream(cin,sys.stdin)
113 self.cin = IOStream(cin,sys.stdin)
114 self.cout = IOStream(cout,sys.stdout)
114 self.cout = IOStream(cout,sys.stdout)
115 self.cerr = IOStream(cerr,sys.stderr)
115 self.cerr = IOStream(cerr,sys.stderr)
116
116
117 # Global variable to be used for all I/O
117 # Global variable to be used for all I/O
118 Term = IOTerm()
118 Term = IOTerm()
119
119
120 import IPython.rlineimpl as readline
120 import IPython.rlineimpl as readline
121 # Remake Term to use the readline i/o facilities
121 # Remake Term to use the readline i/o facilities
122 if sys.platform == 'win32' and readline.have_readline:
122 if sys.platform == 'win32' and readline.have_readline:
123
123
124 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
124 Term = IOTerm(cout=readline._outputfile,cerr=readline._outputfile)
125
125
126
126
127 #****************************************************************************
127 #****************************************************************************
128 # Generic warning/error printer, used by everything else
128 # Generic warning/error printer, used by everything else
129 def warn(msg,level=2,exit_val=1):
129 def warn(msg,level=2,exit_val=1):
130 """Standard warning printer. Gives formatting consistency.
130 """Standard warning printer. Gives formatting consistency.
131
131
132 Output is sent to Term.cerr (sys.stderr by default).
132 Output is sent to Term.cerr (sys.stderr by default).
133
133
134 Options:
134 Options:
135
135
136 -level(2): allows finer control:
136 -level(2): allows finer control:
137 0 -> Do nothing, dummy function.
137 0 -> Do nothing, dummy function.
138 1 -> Print message.
138 1 -> Print message.
139 2 -> Print 'WARNING:' + message. (Default level).
139 2 -> Print 'WARNING:' + message. (Default level).
140 3 -> Print 'ERROR:' + message.
140 3 -> Print 'ERROR:' + message.
141 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
141 4 -> Print 'FATAL ERROR:' + message and trigger a sys.exit(exit_val).
142
142
143 -exit_val (1): exit value returned by sys.exit() for a level 4
143 -exit_val (1): exit value returned by sys.exit() for a level 4
144 warning. Ignored for all other levels."""
144 warning. Ignored for all other levels."""
145
145
146 if level>0:
146 if level>0:
147 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
147 header = ['','','WARNING: ','ERROR: ','FATAL ERROR: ']
148 print >> Term.cerr, '%s%s' % (header[level],msg)
148 print >> Term.cerr, '%s%s' % (header[level],msg)
149 if level == 4:
149 if level == 4:
150 print >> Term.cerr,'Exiting.\n'
150 print >> Term.cerr,'Exiting.\n'
151 sys.exit(exit_val)
151 sys.exit(exit_val)
152
152
153 def info(msg):
153 def info(msg):
154 """Equivalent to warn(msg,level=1)."""
154 """Equivalent to warn(msg,level=1)."""
155
155
156 warn(msg,level=1)
156 warn(msg,level=1)
157
157
158 def error(msg):
158 def error(msg):
159 """Equivalent to warn(msg,level=3)."""
159 """Equivalent to warn(msg,level=3)."""
160
160
161 warn(msg,level=3)
161 warn(msg,level=3)
162
162
163 def fatal(msg,exit_val=1):
163 def fatal(msg,exit_val=1):
164 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
164 """Equivalent to warn(msg,exit_val=exit_val,level=4)."""
165
165
166 warn(msg,exit_val=exit_val,level=4)
166 warn(msg,exit_val=exit_val,level=4)
167
167
168 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
169 # Debugging routines
169 # Debugging routines
170 #
170 #
171 def debugx(expr,pre_msg=''):
171 def debugx(expr,pre_msg=''):
172 """Print the value of an expression from the caller's frame.
172 """Print the value of an expression from the caller's frame.
173
173
174 Takes an expression, evaluates it in the caller's frame and prints both
174 Takes an expression, evaluates it in the caller's frame and prints both
175 the given expression and the resulting value (as well as a debug mark
175 the given expression and the resulting value (as well as a debug mark
176 indicating the name of the calling function. The input must be of a form
176 indicating the name of the calling function. The input must be of a form
177 suitable for eval().
177 suitable for eval().
178
178
179 An optional message can be passed, which will be prepended to the printed
179 An optional message can be passed, which will be prepended to the printed
180 expr->value pair."""
180 expr->value pair."""
181
181
182 cf = sys._getframe(1)
182 cf = sys._getframe(1)
183 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
183 print '[DBG:%s] %s%s -> %r' % (cf.f_code.co_name,pre_msg,expr,
184 eval(expr,cf.f_globals,cf.f_locals))
184 eval(expr,cf.f_globals,cf.f_locals))
185
185
186 # deactivate it by uncommenting the following line, which makes it a no-op
186 # deactivate it by uncommenting the following line, which makes it a no-op
187 #def debugx(expr,pre_msg=''): pass
187 #def debugx(expr,pre_msg=''): pass
188
188
189 #----------------------------------------------------------------------------
189 #----------------------------------------------------------------------------
190 StringTypes = types.StringTypes
190 StringTypes = types.StringTypes
191
191
192 # Basic timing functionality
192 # Basic timing functionality
193
193
194 # If possible (Unix), use the resource module instead of time.clock()
194 # If possible (Unix), use the resource module instead of time.clock()
195 try:
195 try:
196 import resource
196 import resource
197 def clocku():
197 def clocku():
198 """clocku() -> floating point number
198 """clocku() -> floating point number
199
199
200 Return the *USER* CPU time in seconds since the start of the process.
200 Return the *USER* CPU time in seconds since the start of the process.
201 This is done via a call to resource.getrusage, so it avoids the
201 This is done via a call to resource.getrusage, so it avoids the
202 wraparound problems in time.clock()."""
202 wraparound problems in time.clock()."""
203
203
204 return resource.getrusage(resource.RUSAGE_SELF)[0]
204 return resource.getrusage(resource.RUSAGE_SELF)[0]
205
205
206 def clocks():
206 def clocks():
207 """clocks() -> floating point number
207 """clocks() -> floating point number
208
208
209 Return the *SYSTEM* CPU time in seconds since the start of the process.
209 Return the *SYSTEM* CPU time in seconds since the start of the process.
210 This is done via a call to resource.getrusage, so it avoids the
210 This is done via a call to resource.getrusage, so it avoids the
211 wraparound problems in time.clock()."""
211 wraparound problems in time.clock()."""
212
212
213 return resource.getrusage(resource.RUSAGE_SELF)[1]
213 return resource.getrusage(resource.RUSAGE_SELF)[1]
214
214
215 def clock():
215 def clock():
216 """clock() -> floating point number
216 """clock() -> floating point number
217
217
218 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
218 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the start of
219 the process. This is done via a call to resource.getrusage, so it
219 the process. This is done via a call to resource.getrusage, so it
220 avoids the wraparound problems in time.clock()."""
220 avoids the wraparound problems in time.clock()."""
221
221
222 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
222 u,s = resource.getrusage(resource.RUSAGE_SELF)[:2]
223 return u+s
223 return u+s
224
224
225 def clock2():
225 def clock2():
226 """clock2() -> (t_user,t_system)
226 """clock2() -> (t_user,t_system)
227
227
228 Similar to clock(), but return a tuple of user/system times."""
228 Similar to clock(), but return a tuple of user/system times."""
229 return resource.getrusage(resource.RUSAGE_SELF)[:2]
229 return resource.getrusage(resource.RUSAGE_SELF)[:2]
230
230
231 except ImportError:
231 except ImportError:
232 # There is no distinction of user/system time under windows, so we just use
232 # There is no distinction of user/system time under windows, so we just use
233 # time.clock() for everything...
233 # time.clock() for everything...
234 clocku = clocks = clock = time.clock
234 clocku = clocks = clock = time.clock
235 def clock2():
235 def clock2():
236 """Under windows, system CPU time can't be measured.
236 """Under windows, system CPU time can't be measured.
237
237
238 This just returns clock() and zero."""
238 This just returns clock() and zero."""
239 return time.clock(),0.0
239 return time.clock(),0.0
240
240
241 def timings_out(reps,func,*args,**kw):
241 def timings_out(reps,func,*args,**kw):
242 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
242 """timings_out(reps,func,*args,**kw) -> (t_total,t_per_call,output)
243
243
244 Execute a function reps times, return a tuple with the elapsed total
244 Execute a function reps times, return a tuple with the elapsed total
245 CPU time in seconds, the time per call and the function's output.
245 CPU time in seconds, the time per call and the function's output.
246
246
247 Under Unix, the return value is the sum of user+system time consumed by
247 Under Unix, the return value is the sum of user+system time consumed by
248 the process, computed via the resource module. This prevents problems
248 the process, computed via the resource module. This prevents problems
249 related to the wraparound effect which the time.clock() function has.
249 related to the wraparound effect which the time.clock() function has.
250
250
251 Under Windows the return value is in wall clock seconds. See the
251 Under Windows the return value is in wall clock seconds. See the
252 documentation for the time module for more details."""
252 documentation for the time module for more details."""
253
253
254 reps = int(reps)
254 reps = int(reps)
255 assert reps >=1, 'reps must be >= 1'
255 assert reps >=1, 'reps must be >= 1'
256 if reps==1:
256 if reps==1:
257 start = clock()
257 start = clock()
258 out = func(*args,**kw)
258 out = func(*args,**kw)
259 tot_time = clock()-start
259 tot_time = clock()-start
260 else:
260 else:
261 rng = xrange(reps-1) # the last time is executed separately to store output
261 rng = xrange(reps-1) # the last time is executed separately to store output
262 start = clock()
262 start = clock()
263 for dummy in rng: func(*args,**kw)
263 for dummy in rng: func(*args,**kw)
264 out = func(*args,**kw) # one last time
264 out = func(*args,**kw) # one last time
265 tot_time = clock()-start
265 tot_time = clock()-start
266 av_time = tot_time / reps
266 av_time = tot_time / reps
267 return tot_time,av_time,out
267 return tot_time,av_time,out
268
268
269 def timings(reps,func,*args,**kw):
269 def timings(reps,func,*args,**kw):
270 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
270 """timings(reps,func,*args,**kw) -> (t_total,t_per_call)
271
271
272 Execute a function reps times, return a tuple with the elapsed total CPU
272 Execute a function reps times, return a tuple with the elapsed total CPU
273 time in seconds and the time per call. These are just the first two values
273 time in seconds and the time per call. These are just the first two values
274 in timings_out()."""
274 in timings_out()."""
275
275
276 return timings_out(reps,func,*args,**kw)[0:2]
276 return timings_out(reps,func,*args,**kw)[0:2]
277
277
278 def timing(func,*args,**kw):
278 def timing(func,*args,**kw):
279 """timing(func,*args,**kw) -> t_total
279 """timing(func,*args,**kw) -> t_total
280
280
281 Execute a function once, return the elapsed total CPU time in
281 Execute a function once, return the elapsed total CPU time in
282 seconds. This is just the first value in timings_out()."""
282 seconds. This is just the first value in timings_out()."""
283
283
284 return timings_out(1,func,*args,**kw)[0]
284 return timings_out(1,func,*args,**kw)[0]
285
285
286 #****************************************************************************
286 #****************************************************************************
287 # file and system
287 # file and system
288
288
289 def arg_split(s,posix=False):
289 def arg_split(s,posix=False):
290 """Split a command line's arguments in a shell-like manner.
290 """Split a command line's arguments in a shell-like manner.
291
291
292 This is a modified version of the standard library's shlex.split()
292 This is a modified version of the standard library's shlex.split()
293 function, but with a default of posix=False for splitting, so that quotes
293 function, but with a default of posix=False for splitting, so that quotes
294 in inputs are respected."""
294 in inputs are respected."""
295
295
296 # XXX - there may be unicode-related problems here!!! I'm not sure that
296 # XXX - there may be unicode-related problems here!!! I'm not sure that
297 # shlex is truly unicode-safe, so it might be necessary to do
297 # shlex is truly unicode-safe, so it might be necessary to do
298 #
298 #
299 # s = s.encode(sys.stdin.encoding)
299 # s = s.encode(sys.stdin.encoding)
300 #
300 #
301 # first, to ensure that shlex gets a normal string. Input from anyone who
301 # first, to ensure that shlex gets a normal string. Input from anyone who
302 # knows more about unicode and shlex than I would be good to have here...
302 # knows more about unicode and shlex than I would be good to have here...
303 lex = shlex.shlex(s, posix=posix)
303 lex = shlex.shlex(s, posix=posix)
304 lex.whitespace_split = True
304 lex.whitespace_split = True
305 return list(lex)
305 return list(lex)
306
306
307 def system(cmd,verbose=0,debug=0,header=''):
307 def system(cmd,verbose=0,debug=0,header=''):
308 """Execute a system command, return its exit status.
308 """Execute a system command, return its exit status.
309
309
310 Options:
310 Options:
311
311
312 - verbose (0): print the command to be executed.
312 - verbose (0): print the command to be executed.
313
313
314 - debug (0): only print, do not actually execute.
314 - debug (0): only print, do not actually execute.
315
315
316 - header (''): Header to print on screen prior to the executed command (it
316 - header (''): Header to print on screen prior to the executed command (it
317 is only prepended to the command, no newlines are added).
317 is only prepended to the command, no newlines are added).
318
318
319 Note: a stateful version of this function is available through the
319 Note: a stateful version of this function is available through the
320 SystemExec class."""
320 SystemExec class."""
321
321
322 stat = 0
322 stat = 0
323 if verbose or debug: print header+cmd
323 if verbose or debug: print header+cmd
324 sys.stdout.flush()
324 sys.stdout.flush()
325 if not debug: stat = os.system(cmd)
325 if not debug: stat = os.system(cmd)
326 return stat
326 return stat
327
327
328 def abbrev_cwd():
328 def abbrev_cwd():
329 """ Return abbreviated version of cwd, e.g. d:mydir """
329 """ Return abbreviated version of cwd, e.g. d:mydir """
330 cwd = os.getcwd().replace('\\','/')
330 cwd = os.getcwd().replace('\\','/')
331 drivepart = ''
331 drivepart = ''
332 tail = cwd
332 tail = cwd
333 if sys.platform == 'win32':
333 if sys.platform == 'win32':
334 if len(cwd) < 4:
334 if len(cwd) < 4:
335 return cwd
335 return cwd
336 drivepart,tail = os.path.splitdrive(cwd)
336 drivepart,tail = os.path.splitdrive(cwd)
337
337
338
338
339 parts = tail.split('/')
339 parts = tail.split('/')
340 if len(parts) > 2:
340 if len(parts) > 2:
341 tail = '/'.join(parts[-2:])
341 tail = '/'.join(parts[-2:])
342
342
343 return (drivepart + (
343 return (drivepart + (
344 cwd == '/' and '/' or tail))
344 cwd == '/' and '/' or tail))
345
345
346
346
347 # This function is used by ipython in a lot of places to make system calls.
347 # This function is used by ipython in a lot of places to make system calls.
348 # We need it to be slightly different under win32, due to the vagaries of
348 # We need it to be slightly different under win32, due to the vagaries of
349 # 'network shares'. A win32 override is below.
349 # 'network shares'. A win32 override is below.
350
350
351 def shell(cmd,verbose=0,debug=0,header=''):
351 def shell(cmd,verbose=0,debug=0,header=''):
352 """Execute a command in the system shell, always return None.
352 """Execute a command in the system shell, always return None.
353
353
354 Options:
354 Options:
355
355
356 - verbose (0): print the command to be executed.
356 - verbose (0): print the command to be executed.
357
357
358 - debug (0): only print, do not actually execute.
358 - debug (0): only print, do not actually execute.
359
359
360 - header (''): Header to print on screen prior to the executed command (it
360 - header (''): Header to print on screen prior to the executed command (it
361 is only prepended to the command, no newlines are added).
361 is only prepended to the command, no newlines are added).
362
362
363 Note: this is similar to genutils.system(), but it returns None so it can
363 Note: this is similar to genutils.system(), but it returns None so it can
364 be conveniently used in interactive loops without getting the return value
364 be conveniently used in interactive loops without getting the return value
365 (typically 0) printed many times."""
365 (typically 0) printed many times."""
366
366
367 stat = 0
367 stat = 0
368 if verbose or debug: print header+cmd
368 if verbose or debug: print header+cmd
369 # flush stdout so we don't mangle python's buffering
369 # flush stdout so we don't mangle python's buffering
370 sys.stdout.flush()
370 sys.stdout.flush()
371
371
372 if not debug:
372 if not debug:
373 platutils.set_term_title("IPy " + cmd)
373 platutils.set_term_title("IPy " + cmd)
374 os.system(cmd)
374 os.system(cmd)
375 platutils.set_term_title("IPy " + abbrev_cwd())
375 platutils.set_term_title("IPy " + abbrev_cwd())
376
376
377 # override shell() for win32 to deal with network shares
377 # override shell() for win32 to deal with network shares
378 if os.name in ('nt','dos'):
378 if os.name in ('nt','dos'):
379
379
380 shell_ori = shell
380 shell_ori = shell
381
381
382 def shell(cmd,verbose=0,debug=0,header=''):
382 def shell(cmd,verbose=0,debug=0,header=''):
383 if os.getcwd().startswith(r"\\"):
383 if os.getcwd().startswith(r"\\"):
384 path = os.getcwd()
384 path = os.getcwd()
385 # change to c drive (cannot be on UNC-share when issuing os.system,
385 # change to c drive (cannot be on UNC-share when issuing os.system,
386 # as cmd.exe cannot handle UNC addresses)
386 # as cmd.exe cannot handle UNC addresses)
387 os.chdir("c:")
387 os.chdir("c:")
388 # issue pushd to the UNC-share and then run the command
388 # issue pushd to the UNC-share and then run the command
389 try:
389 try:
390 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
390 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
391 finally:
391 finally:
392 os.chdir(path)
392 os.chdir(path)
393 else:
393 else:
394 shell_ori(cmd,verbose,debug,header)
394 shell_ori(cmd,verbose,debug,header)
395
395
396 shell.__doc__ = shell_ori.__doc__
396 shell.__doc__ = shell_ori.__doc__
397
397
398 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
398 def getoutput(cmd,verbose=0,debug=0,header='',split=0):
399 """Dummy substitute for perl's backquotes.
399 """Dummy substitute for perl's backquotes.
400
400
401 Executes a command and returns the output.
401 Executes a command and returns the output.
402
402
403 Accepts the same arguments as system(), plus:
403 Accepts the same arguments as system(), plus:
404
404
405 - split(0): if true, the output is returned as a list split on newlines.
405 - split(0): if true, the output is returned as a list split on newlines.
406
406
407 Note: a stateful version of this function is available through the
407 Note: a stateful version of this function is available through the
408 SystemExec class.
408 SystemExec class.
409
409
410 This is pretty much deprecated and rarely used,
410 This is pretty much deprecated and rarely used,
411 genutils.getoutputerror may be what you need.
411 genutils.getoutputerror may be what you need.
412
412
413 """
413 """
414
414
415 if verbose or debug: print header+cmd
415 if verbose or debug: print header+cmd
416 if not debug:
416 if not debug:
417 output = os.popen(cmd).read()
417 output = os.popen(cmd).read()
418 # stipping last \n is here for backwards compat.
418 # stipping last \n is here for backwards compat.
419 if output.endswith('\n'):
419 if output.endswith('\n'):
420 output = output[:-1]
420 output = output[:-1]
421 if split:
421 if split:
422 return output.split('\n')
422 return output.split('\n')
423 else:
423 else:
424 return output
424 return output
425
425
426 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
426 def getoutputerror(cmd,verbose=0,debug=0,header='',split=0):
427 """Return (standard output,standard error) of executing cmd in a shell.
427 """Return (standard output,standard error) of executing cmd in a shell.
428
428
429 Accepts the same arguments as system(), plus:
429 Accepts the same arguments as system(), plus:
430
430
431 - split(0): if true, each of stdout/err is returned as a list split on
431 - split(0): if true, each of stdout/err is returned as a list split on
432 newlines.
432 newlines.
433
433
434 Note: a stateful version of this function is available through the
434 Note: a stateful version of this function is available through the
435 SystemExec class."""
435 SystemExec class."""
436
436
437 if verbose or debug: print header+cmd
437 if verbose or debug: print header+cmd
438 if not cmd:
438 if not cmd:
439 if split:
439 if split:
440 return [],[]
440 return [],[]
441 else:
441 else:
442 return '',''
442 return '',''
443 if not debug:
443 if not debug:
444 pin,pout,perr = os.popen3(cmd)
444 pin,pout,perr = os.popen3(cmd)
445 tout = pout.read().rstrip()
445 tout = pout.read().rstrip()
446 terr = perr.read().rstrip()
446 terr = perr.read().rstrip()
447 pin.close()
447 pin.close()
448 pout.close()
448 pout.close()
449 perr.close()
449 perr.close()
450 if split:
450 if split:
451 return tout.split('\n'),terr.split('\n')
451 return tout.split('\n'),terr.split('\n')
452 else:
452 else:
453 return tout,terr
453 return tout,terr
454
454
455 # for compatibility with older naming conventions
455 # for compatibility with older naming conventions
456 xsys = system
456 xsys = system
457 bq = getoutput
457 bq = getoutput
458
458
459 class SystemExec:
459 class SystemExec:
460 """Access the system and getoutput functions through a stateful interface.
460 """Access the system and getoutput functions through a stateful interface.
461
461
462 Note: here we refer to the system and getoutput functions from this
462 Note: here we refer to the system and getoutput functions from this
463 library, not the ones from the standard python library.
463 library, not the ones from the standard python library.
464
464
465 This class offers the system and getoutput functions as methods, but the
465 This class offers the system and getoutput functions as methods, but the
466 verbose, debug and header parameters can be set for the instance (at
466 verbose, debug and header parameters can be set for the instance (at
467 creation time or later) so that they don't need to be specified on each
467 creation time or later) so that they don't need to be specified on each
468 call.
468 call.
469
469
470 For efficiency reasons, there's no way to override the parameters on a
470 For efficiency reasons, there's no way to override the parameters on a
471 per-call basis other than by setting instance attributes. If you need
471 per-call basis other than by setting instance attributes. If you need
472 local overrides, it's best to directly call system() or getoutput().
472 local overrides, it's best to directly call system() or getoutput().
473
473
474 The following names are provided as alternate options:
474 The following names are provided as alternate options:
475 - xsys: alias to system
475 - xsys: alias to system
476 - bq: alias to getoutput
476 - bq: alias to getoutput
477
477
478 An instance can then be created as:
478 An instance can then be created as:
479 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
479 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
480 """
480 """
481
481
482 def __init__(self,verbose=0,debug=0,header='',split=0):
482 def __init__(self,verbose=0,debug=0,header='',split=0):
483 """Specify the instance's values for verbose, debug and header."""
483 """Specify the instance's values for verbose, debug and header."""
484 setattr_list(self,'verbose debug header split')
484 setattr_list(self,'verbose debug header split')
485
485
486 def system(self,cmd):
486 def system(self,cmd):
487 """Stateful interface to system(), with the same keyword parameters."""
487 """Stateful interface to system(), with the same keyword parameters."""
488
488
489 system(cmd,self.verbose,self.debug,self.header)
489 system(cmd,self.verbose,self.debug,self.header)
490
490
491 def shell(self,cmd):
491 def shell(self,cmd):
492 """Stateful interface to shell(), with the same keyword parameters."""
492 """Stateful interface to shell(), with the same keyword parameters."""
493
493
494 shell(cmd,self.verbose,self.debug,self.header)
494 shell(cmd,self.verbose,self.debug,self.header)
495
495
496 xsys = system # alias
496 xsys = system # alias
497
497
498 def getoutput(self,cmd):
498 def getoutput(self,cmd):
499 """Stateful interface to getoutput()."""
499 """Stateful interface to getoutput()."""
500
500
501 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
501 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
502
502
503 def getoutputerror(self,cmd):
503 def getoutputerror(self,cmd):
504 """Stateful interface to getoutputerror()."""
504 """Stateful interface to getoutputerror()."""
505
505
506 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
506 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
507
507
508 bq = getoutput # alias
508 bq = getoutput # alias
509
509
510 #-----------------------------------------------------------------------------
510 #-----------------------------------------------------------------------------
511 def mutex_opts(dict,ex_op):
511 def mutex_opts(dict,ex_op):
512 """Check for presence of mutually exclusive keys in a dict.
512 """Check for presence of mutually exclusive keys in a dict.
513
513
514 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
514 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
515 for op1,op2 in ex_op:
515 for op1,op2 in ex_op:
516 if op1 in dict and op2 in dict:
516 if op1 in dict and op2 in dict:
517 raise ValueError,'\n*** ERROR in Arguments *** '\
517 raise ValueError,'\n*** ERROR in Arguments *** '\
518 'Options '+op1+' and '+op2+' are mutually exclusive.'
518 'Options '+op1+' and '+op2+' are mutually exclusive.'
519
519
520 #-----------------------------------------------------------------------------
520 #-----------------------------------------------------------------------------
521 def get_py_filename(name):
521 def get_py_filename(name):
522 """Return a valid python filename in the current directory.
522 """Return a valid python filename in the current directory.
523
523
524 If the given name is not a file, it adds '.py' and searches again.
524 If the given name is not a file, it adds '.py' and searches again.
525 Raises IOError with an informative message if the file isn't found."""
525 Raises IOError with an informative message if the file isn't found."""
526
526
527 name = os.path.expanduser(name)
527 name = os.path.expanduser(name)
528 if not os.path.isfile(name) and not name.endswith('.py'):
528 if not os.path.isfile(name) and not name.endswith('.py'):
529 name += '.py'
529 name += '.py'
530 if os.path.isfile(name):
530 if os.path.isfile(name):
531 return name
531 return name
532 else:
532 else:
533 raise IOError,'File `%s` not found.' % name
533 raise IOError,'File `%s` not found.' % name
534
534
535 #-----------------------------------------------------------------------------
535 #-----------------------------------------------------------------------------
536 def filefind(fname,alt_dirs = None):
536 def filefind(fname,alt_dirs = None):
537 """Return the given filename either in the current directory, if it
537 """Return the given filename either in the current directory, if it
538 exists, or in a specified list of directories.
538 exists, or in a specified list of directories.
539
539
540 ~ expansion is done on all file and directory names.
540 ~ expansion is done on all file and directory names.
541
541
542 Upon an unsuccessful search, raise an IOError exception."""
542 Upon an unsuccessful search, raise an IOError exception."""
543
543
544 if alt_dirs is None:
544 if alt_dirs is None:
545 try:
545 try:
546 alt_dirs = get_home_dir()
546 alt_dirs = get_home_dir()
547 except HomeDirError:
547 except HomeDirError:
548 alt_dirs = os.getcwd()
548 alt_dirs = os.getcwd()
549 search = [fname] + list_strings(alt_dirs)
549 search = [fname] + list_strings(alt_dirs)
550 search = map(os.path.expanduser,search)
550 search = map(os.path.expanduser,search)
551 #print 'search list for',fname,'list:',search # dbg
551 #print 'search list for',fname,'list:',search # dbg
552 fname = search[0]
552 fname = search[0]
553 if os.path.isfile(fname):
553 if os.path.isfile(fname):
554 return fname
554 return fname
555 for direc in search[1:]:
555 for direc in search[1:]:
556 testname = os.path.join(direc,fname)
556 testname = os.path.join(direc,fname)
557 #print 'testname',testname # dbg
557 #print 'testname',testname # dbg
558 if os.path.isfile(testname):
558 if os.path.isfile(testname):
559 return testname
559 return testname
560 raise IOError,'File' + `fname` + \
560 raise IOError,'File' + `fname` + \
561 ' not found in current or supplied directories:' + `alt_dirs`
561 ' not found in current or supplied directories:' + `alt_dirs`
562
562
563 #----------------------------------------------------------------------------
563 #----------------------------------------------------------------------------
564 def file_read(filename):
564 def file_read(filename):
565 """Read a file and close it. Returns the file source."""
565 """Read a file and close it. Returns the file source."""
566 fobj = open(filename,'r');
566 fobj = open(filename,'r');
567 source = fobj.read();
567 source = fobj.read();
568 fobj.close()
568 fobj.close()
569 return source
569 return source
570
570
571 def file_readlines(filename):
571 def file_readlines(filename):
572 """Read a file and close it. Returns the file source using readlines()."""
572 """Read a file and close it. Returns the file source using readlines()."""
573 fobj = open(filename,'r');
573 fobj = open(filename,'r');
574 lines = fobj.readlines();
574 lines = fobj.readlines();
575 fobj.close()
575 fobj.close()
576 return lines
576 return lines
577
577
578 #----------------------------------------------------------------------------
578 #----------------------------------------------------------------------------
579 def target_outdated(target,deps):
579 def target_outdated(target,deps):
580 """Determine whether a target is out of date.
580 """Determine whether a target is out of date.
581
581
582 target_outdated(target,deps) -> 1/0
582 target_outdated(target,deps) -> 1/0
583
583
584 deps: list of filenames which MUST exist.
584 deps: list of filenames which MUST exist.
585 target: single filename which may or may not exist.
585 target: single filename which may or may not exist.
586
586
587 If target doesn't exist or is older than any file listed in deps, return
587 If target doesn't exist or is older than any file listed in deps, return
588 true, otherwise return false.
588 true, otherwise return false.
589 """
589 """
590 try:
590 try:
591 target_time = os.path.getmtime(target)
591 target_time = os.path.getmtime(target)
592 except os.error:
592 except os.error:
593 return 1
593 return 1
594 for dep in deps:
594 for dep in deps:
595 dep_time = os.path.getmtime(dep)
595 dep_time = os.path.getmtime(dep)
596 if dep_time > target_time:
596 if dep_time > target_time:
597 #print "For target",target,"Dep failed:",dep # dbg
597 #print "For target",target,"Dep failed:",dep # dbg
598 #print "times (dep,tar):",dep_time,target_time # dbg
598 #print "times (dep,tar):",dep_time,target_time # dbg
599 return 1
599 return 1
600 return 0
600 return 0
601
601
602 #-----------------------------------------------------------------------------
602 #-----------------------------------------------------------------------------
603 def target_update(target,deps,cmd):
603 def target_update(target,deps,cmd):
604 """Update a target with a given command given a list of dependencies.
604 """Update a target with a given command given a list of dependencies.
605
605
606 target_update(target,deps,cmd) -> runs cmd if target is outdated.
606 target_update(target,deps,cmd) -> runs cmd if target is outdated.
607
607
608 This is just a wrapper around target_outdated() which calls the given
608 This is just a wrapper around target_outdated() which calls the given
609 command if target is outdated."""
609 command if target is outdated."""
610
610
611 if target_outdated(target,deps):
611 if target_outdated(target,deps):
612 xsys(cmd)
612 xsys(cmd)
613
613
614 #----------------------------------------------------------------------------
614 #----------------------------------------------------------------------------
615 def unquote_ends(istr):
615 def unquote_ends(istr):
616 """Remove a single pair of quotes from the endpoints of a string."""
616 """Remove a single pair of quotes from the endpoints of a string."""
617
617
618 if not istr:
618 if not istr:
619 return istr
619 return istr
620 if (istr[0]=="'" and istr[-1]=="'") or \
620 if (istr[0]=="'" and istr[-1]=="'") or \
621 (istr[0]=='"' and istr[-1]=='"'):
621 (istr[0]=='"' and istr[-1]=='"'):
622 return istr[1:-1]
622 return istr[1:-1]
623 else:
623 else:
624 return istr
624 return istr
625
625
626 #----------------------------------------------------------------------------
626 #----------------------------------------------------------------------------
627 def process_cmdline(argv,names=[],defaults={},usage=''):
627 def process_cmdline(argv,names=[],defaults={},usage=''):
628 """ Process command-line options and arguments.
628 """ Process command-line options and arguments.
629
629
630 Arguments:
630 Arguments:
631
631
632 - argv: list of arguments, typically sys.argv.
632 - argv: list of arguments, typically sys.argv.
633
633
634 - names: list of option names. See DPyGetOpt docs for details on options
634 - names: list of option names. See DPyGetOpt docs for details on options
635 syntax.
635 syntax.
636
636
637 - defaults: dict of default values.
637 - defaults: dict of default values.
638
638
639 - usage: optional usage notice to print if a wrong argument is passed.
639 - usage: optional usage notice to print if a wrong argument is passed.
640
640
641 Return a dict of options and a list of free arguments."""
641 Return a dict of options and a list of free arguments."""
642
642
643 getopt = DPyGetOpt.DPyGetOpt()
643 getopt = DPyGetOpt.DPyGetOpt()
644 getopt.setIgnoreCase(0)
644 getopt.setIgnoreCase(0)
645 getopt.parseConfiguration(names)
645 getopt.parseConfiguration(names)
646
646
647 try:
647 try:
648 getopt.processArguments(argv)
648 getopt.processArguments(argv)
649 except DPyGetOpt.ArgumentError, exc:
649 except DPyGetOpt.ArgumentError, exc:
650 print usage
650 print usage
651 warn('"%s"' % exc,level=4)
651 warn('"%s"' % exc,level=4)
652
652
653 defaults.update(getopt.optionValues)
653 defaults.update(getopt.optionValues)
654 args = getopt.freeValues
654 args = getopt.freeValues
655
655
656 return defaults,args
656 return defaults,args
657
657
658 #----------------------------------------------------------------------------
658 #----------------------------------------------------------------------------
659 def optstr2types(ostr):
659 def optstr2types(ostr):
660 """Convert a string of option names to a dict of type mappings.
660 """Convert a string of option names to a dict of type mappings.
661
661
662 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
662 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
663
663
664 This is used to get the types of all the options in a string formatted
664 This is used to get the types of all the options in a string formatted
665 with the conventions of DPyGetOpt. The 'type' None is used for options
665 with the conventions of DPyGetOpt. The 'type' None is used for options
666 which are strings (they need no further conversion). This function's main
666 which are strings (they need no further conversion). This function's main
667 use is to get a typemap for use with read_dict().
667 use is to get a typemap for use with read_dict().
668 """
668 """
669
669
670 typeconv = {None:'',int:'',float:''}
670 typeconv = {None:'',int:'',float:''}
671 typemap = {'s':None,'i':int,'f':float}
671 typemap = {'s':None,'i':int,'f':float}
672 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
672 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
673
673
674 for w in ostr.split():
674 for w in ostr.split():
675 oname,alias,otype = opt_re.match(w).groups()
675 oname,alias,otype = opt_re.match(w).groups()
676 if otype == '' or alias == '!': # simple switches are integers too
676 if otype == '' or alias == '!': # simple switches are integers too
677 otype = 'i'
677 otype = 'i'
678 typeconv[typemap[otype]] += oname + ' '
678 typeconv[typemap[otype]] += oname + ' '
679 return typeconv
679 return typeconv
680
680
681 #----------------------------------------------------------------------------
681 #----------------------------------------------------------------------------
682 def read_dict(filename,type_conv=None,**opt):
682 def read_dict(filename,type_conv=None,**opt):
683 r"""Read a dictionary of key=value pairs from an input file, optionally
683 r"""Read a dictionary of key=value pairs from an input file, optionally
684 performing conversions on the resulting values.
684 performing conversions on the resulting values.
685
685
686 read_dict(filename,type_conv,**opt) -> dict
686 read_dict(filename,type_conv,**opt) -> dict
687
687
688 Only one value per line is accepted, the format should be
688 Only one value per line is accepted, the format should be
689 # optional comments are ignored
689 # optional comments are ignored
690 key value\n
690 key value\n
691
691
692 Args:
692 Args:
693
693
694 - type_conv: A dictionary specifying which keys need to be converted to
694 - type_conv: A dictionary specifying which keys need to be converted to
695 which types. By default all keys are read as strings. This dictionary
695 which types. By default all keys are read as strings. This dictionary
696 should have as its keys valid conversion functions for strings
696 should have as its keys valid conversion functions for strings
697 (int,long,float,complex, or your own). The value for each key
697 (int,long,float,complex, or your own). The value for each key
698 (converter) should be a whitespace separated string containing the names
698 (converter) should be a whitespace separated string containing the names
699 of all the entries in the file to be converted using that function. For
699 of all the entries in the file to be converted using that function. For
700 keys to be left alone, use None as the conversion function (only needed
700 keys to be left alone, use None as the conversion function (only needed
701 with purge=1, see below).
701 with purge=1, see below).
702
702
703 - opt: dictionary with extra options as below (default in parens)
703 - opt: dictionary with extra options as below (default in parens)
704
704
705 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
705 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
706 of the dictionary to be returned. If purge is going to be used, the
706 of the dictionary to be returned. If purge is going to be used, the
707 set of keys to be left as strings also has to be explicitly specified
707 set of keys to be left as strings also has to be explicitly specified
708 using the (non-existent) conversion function None.
708 using the (non-existent) conversion function None.
709
709
710 fs(None): field separator. This is the key/value separator to be used
710 fs(None): field separator. This is the key/value separator to be used
711 when parsing the file. The None default means any whitespace [behavior
711 when parsing the file. The None default means any whitespace [behavior
712 of string.split()].
712 of string.split()].
713
713
714 strip(0): if 1, strip string values of leading/trailinig whitespace.
714 strip(0): if 1, strip string values of leading/trailinig whitespace.
715
715
716 warn(1): warning level if requested keys are not found in file.
716 warn(1): warning level if requested keys are not found in file.
717 - 0: silently ignore.
717 - 0: silently ignore.
718 - 1: inform but proceed.
718 - 1: inform but proceed.
719 - 2: raise KeyError exception.
719 - 2: raise KeyError exception.
720
720
721 no_empty(0): if 1, remove keys with whitespace strings as a value.
721 no_empty(0): if 1, remove keys with whitespace strings as a value.
722
722
723 unique([]): list of keys (or space separated string) which can't be
723 unique([]): list of keys (or space separated string) which can't be
724 repeated. If one such key is found in the file, each new instance
724 repeated. If one such key is found in the file, each new instance
725 overwrites the previous one. For keys not listed here, the behavior is
725 overwrites the previous one. For keys not listed here, the behavior is
726 to make a list of all appearances.
726 to make a list of all appearances.
727
727
728 Example:
728 Example:
729
729
730 If the input file test.ini contains (we put it in a string to keep the test
730 If the input file test.ini contains (we put it in a string to keep the test
731 self-contained):
731 self-contained):
732
732
733 >>> test_ini = '''\
733 >>> test_ini = '''\
734 ... i 3
734 ... i 3
735 ... x 4.5
735 ... x 4.5
736 ... y 5.5
736 ... y 5.5
737 ... s hi ho'''
737 ... s hi ho'''
738
738
739 Then we can use it as follows:
739 Then we can use it as follows:
740 >>> type_conv={int:'i',float:'x',None:'s'}
740 >>> type_conv={int:'i',float:'x',None:'s'}
741
741
742 >>> d = read_dict(test_ini)
742 >>> d = read_dict(test_ini)
743
743
744 >>> sorted(d.items())
744 >>> sorted(d.items())
745 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
745 [('i', '3'), ('s', 'hi ho'), ('x', '4.5'), ('y', '5.5')]
746
746
747 >>> d = read_dict(test_ini,type_conv)
747 >>> d = read_dict(test_ini,type_conv)
748
748
749 >>> sorted(d.items())
749 >>> sorted(d.items())
750 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
750 [('i', 3), ('s', 'hi ho'), ('x', 4.5), ('y', '5.5')]
751
751
752 >>> d = read_dict(test_ini,type_conv,purge=True)
752 >>> d = read_dict(test_ini,type_conv,purge=True)
753
753
754 >>> sorted(d.items())
754 >>> sorted(d.items())
755 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
755 [('i', 3), ('s', 'hi ho'), ('x', 4.5)]
756 """
756 """
757
757
758 # starting config
758 # starting config
759 opt.setdefault('purge',0)
759 opt.setdefault('purge',0)
760 opt.setdefault('fs',None) # field sep defaults to any whitespace
760 opt.setdefault('fs',None) # field sep defaults to any whitespace
761 opt.setdefault('strip',0)
761 opt.setdefault('strip',0)
762 opt.setdefault('warn',1)
762 opt.setdefault('warn',1)
763 opt.setdefault('no_empty',0)
763 opt.setdefault('no_empty',0)
764 opt.setdefault('unique','')
764 opt.setdefault('unique','')
765 if type(opt['unique']) in StringTypes:
765 if type(opt['unique']) in StringTypes:
766 unique_keys = qw(opt['unique'])
766 unique_keys = qw(opt['unique'])
767 elif type(opt['unique']) in (types.TupleType,types.ListType):
767 elif type(opt['unique']) in (types.TupleType,types.ListType):
768 unique_keys = opt['unique']
768 unique_keys = opt['unique']
769 else:
769 else:
770 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
770 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
771
771
772 dict = {}
772 dict = {}
773
773
774 # first read in table of values as strings
774 # first read in table of values as strings
775 if '\n' in filename:
775 if '\n' in filename:
776 lines = filename.splitlines()
776 lines = filename.splitlines()
777 file = None
777 file = None
778 else:
778 else:
779 file = open(filename,'r')
779 file = open(filename,'r')
780 lines = file.readlines()
780 lines = file.readlines()
781 for line in lines:
781 for line in lines:
782 line = line.strip()
782 line = line.strip()
783 if len(line) and line[0]=='#': continue
783 if len(line) and line[0]=='#': continue
784 if len(line)>0:
784 if len(line)>0:
785 lsplit = line.split(opt['fs'],1)
785 lsplit = line.split(opt['fs'],1)
786 try:
786 try:
787 key,val = lsplit
787 key,val = lsplit
788 except ValueError:
788 except ValueError:
789 key,val = lsplit[0],''
789 key,val = lsplit[0],''
790 key = key.strip()
790 key = key.strip()
791 if opt['strip']: val = val.strip()
791 if opt['strip']: val = val.strip()
792 if val == "''" or val == '""': val = ''
792 if val == "''" or val == '""': val = ''
793 if opt['no_empty'] and (val=='' or val.isspace()):
793 if opt['no_empty'] and (val=='' or val.isspace()):
794 continue
794 continue
795 # if a key is found more than once in the file, build a list
795 # if a key is found more than once in the file, build a list
796 # unless it's in the 'unique' list. In that case, last found in file
796 # unless it's in the 'unique' list. In that case, last found in file
797 # takes precedence. User beware.
797 # takes precedence. User beware.
798 try:
798 try:
799 if dict[key] and key in unique_keys:
799 if dict[key] and key in unique_keys:
800 dict[key] = val
800 dict[key] = val
801 elif type(dict[key]) is types.ListType:
801 elif type(dict[key]) is types.ListType:
802 dict[key].append(val)
802 dict[key].append(val)
803 else:
803 else:
804 dict[key] = [dict[key],val]
804 dict[key] = [dict[key],val]
805 except KeyError:
805 except KeyError:
806 dict[key] = val
806 dict[key] = val
807 # purge if requested
807 # purge if requested
808 if opt['purge']:
808 if opt['purge']:
809 accepted_keys = qwflat(type_conv.values())
809 accepted_keys = qwflat(type_conv.values())
810 for key in dict.keys():
810 for key in dict.keys():
811 if key in accepted_keys: continue
811 if key in accepted_keys: continue
812 del(dict[key])
812 del(dict[key])
813 # now convert if requested
813 # now convert if requested
814 if type_conv==None: return dict
814 if type_conv==None: return dict
815 conversions = type_conv.keys()
815 conversions = type_conv.keys()
816 try: conversions.remove(None)
816 try: conversions.remove(None)
817 except: pass
817 except: pass
818 for convert in conversions:
818 for convert in conversions:
819 for val in qw(type_conv[convert]):
819 for val in qw(type_conv[convert]):
820 try:
820 try:
821 dict[val] = convert(dict[val])
821 dict[val] = convert(dict[val])
822 except KeyError,e:
822 except KeyError,e:
823 if opt['warn'] == 0:
823 if opt['warn'] == 0:
824 pass
824 pass
825 elif opt['warn'] == 1:
825 elif opt['warn'] == 1:
826 print >>sys.stderr, 'Warning: key',val,\
826 print >>sys.stderr, 'Warning: key',val,\
827 'not found in file',filename
827 'not found in file',filename
828 elif opt['warn'] == 2:
828 elif opt['warn'] == 2:
829 raise KeyError,e
829 raise KeyError,e
830 else:
830 else:
831 raise ValueError,'Warning level must be 0,1 or 2'
831 raise ValueError,'Warning level must be 0,1 or 2'
832
832
833 return dict
833 return dict
834
834
835 #----------------------------------------------------------------------------
835 #----------------------------------------------------------------------------
836 def flag_calls(func):
836 def flag_calls(func):
837 """Wrap a function to detect and flag when it gets called.
837 """Wrap a function to detect and flag when it gets called.
838
838
839 This is a decorator which takes a function and wraps it in a function with
839 This is a decorator which takes a function and wraps it in a function with
840 a 'called' attribute. wrapper.called is initialized to False.
840 a 'called' attribute. wrapper.called is initialized to False.
841
841
842 The wrapper.called attribute is set to False right before each call to the
842 The wrapper.called attribute is set to False right before each call to the
843 wrapped function, so if the call fails it remains False. After the call
843 wrapped function, so if the call fails it remains False. After the call
844 completes, wrapper.called is set to True and the output is returned.
844 completes, wrapper.called is set to True and the output is returned.
845
845
846 Testing for truth in wrapper.called allows you to determine if a call to
846 Testing for truth in wrapper.called allows you to determine if a call to
847 func() was attempted and succeeded."""
847 func() was attempted and succeeded."""
848
848
849 def wrapper(*args,**kw):
849 def wrapper(*args,**kw):
850 wrapper.called = False
850 wrapper.called = False
851 out = func(*args,**kw)
851 out = func(*args,**kw)
852 wrapper.called = True
852 wrapper.called = True
853 return out
853 return out
854
854
855 wrapper.called = False
855 wrapper.called = False
856 wrapper.__doc__ = func.__doc__
856 wrapper.__doc__ = func.__doc__
857 return wrapper
857 return wrapper
858
858
859 #----------------------------------------------------------------------------
859 #----------------------------------------------------------------------------
860 def dhook_wrap(func,*a,**k):
860 def dhook_wrap(func,*a,**k):
861 """Wrap a function call in a sys.displayhook controller.
861 """Wrap a function call in a sys.displayhook controller.
862
862
863 Returns a wrapper around func which calls func, with all its arguments and
863 Returns a wrapper around func which calls func, with all its arguments and
864 keywords unmodified, using the default sys.displayhook. Since IPython
864 keywords unmodified, using the default sys.displayhook. Since IPython
865 modifies sys.displayhook, it breaks the behavior of certain systems that
865 modifies sys.displayhook, it breaks the behavior of certain systems that
866 rely on the default behavior, notably doctest.
866 rely on the default behavior, notably doctest.
867 """
867 """
868
868
869 def f(*a,**k):
869 def f(*a,**k):
870
870
871 dhook_s = sys.displayhook
871 dhook_s = sys.displayhook
872 sys.displayhook = sys.__displayhook__
872 sys.displayhook = sys.__displayhook__
873 try:
873 try:
874 out = func(*a,**k)
874 out = func(*a,**k)
875 finally:
875 finally:
876 sys.displayhook = dhook_s
876 sys.displayhook = dhook_s
877
877
878 return out
878 return out
879
879
880 f.__doc__ = func.__doc__
880 f.__doc__ = func.__doc__
881 return f
881 return f
882
882
883 #----------------------------------------------------------------------------
883 #----------------------------------------------------------------------------
884 def doctest_reload():
884 def doctest_reload():
885 """Properly reload doctest to reuse it interactively.
885 """Properly reload doctest to reuse it interactively.
886
886
887 This routine:
887 This routine:
888
888
889 - reloads doctest
889 - reloads doctest
890
890
891 - resets its global 'master' attribute to None, so that multiple uses of
891 - resets its global 'master' attribute to None, so that multiple uses of
892 the module interactively don't produce cumulative reports.
892 the module interactively don't produce cumulative reports.
893
893
894 - Monkeypatches its core test runner method to protect it from IPython's
894 - Monkeypatches its core test runner method to protect it from IPython's
895 modified displayhook. Doctest expects the default displayhook behavior
895 modified displayhook. Doctest expects the default displayhook behavior
896 deep down, so our modification breaks it completely. For this reason, a
896 deep down, so our modification breaks it completely. For this reason, a
897 hard monkeypatch seems like a reasonable solution rather than asking
897 hard monkeypatch seems like a reasonable solution rather than asking
898 users to manually use a different doctest runner when under IPython."""
898 users to manually use a different doctest runner when under IPython."""
899
899
900 import doctest
900 import doctest
901 reload(doctest)
901 reload(doctest)
902 doctest.master=None
902 doctest.master=None
903
903
904 try:
904 try:
905 doctest.DocTestRunner
905 doctest.DocTestRunner
906 except AttributeError:
906 except AttributeError:
907 # This is only for python 2.3 compatibility, remove once we move to
907 # This is only for python 2.3 compatibility, remove once we move to
908 # 2.4 only.
908 # 2.4 only.
909 pass
909 pass
910 else:
910 else:
911 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
911 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
912
912
913 #----------------------------------------------------------------------------
913 #----------------------------------------------------------------------------
914 class HomeDirError(Error):
914 class HomeDirError(Error):
915 pass
915 pass
916
916
917 def get_home_dir():
917 def get_home_dir():
918 """Return the closest possible equivalent to a 'home' directory.
918 """Return the closest possible equivalent to a 'home' directory.
919
919
920 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
920 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
921
921
922 Currently only Posix and NT are implemented, a HomeDirError exception is
922 Currently only Posix and NT are implemented, a HomeDirError exception is
923 raised for all other OSes. """
923 raised for all other OSes. """
924
924
925 isdir = os.path.isdir
925 isdir = os.path.isdir
926 env = os.environ
926 env = os.environ
927
927
928 # first, check py2exe distribution root directory for _ipython.
928 # first, check py2exe distribution root directory for _ipython.
929 # This overrides all. Normally does not exist.
929 # This overrides all. Normally does not exist.
930
930
931 if '\\library.zip\\' in IPython.__file__.lower():
931 if '\\library.zip\\' in IPython.__file__.lower():
932 root, rest = IPython.__file__.lower().split('library.zip')
932 root, rest = IPython.__file__.lower().split('library.zip')
933 if isdir(root + '_ipython'):
933 if isdir(root + '_ipython'):
934 os.environ["IPYKITROOT"] = root.rstrip('\\')
934 os.environ["IPYKITROOT"] = root.rstrip('\\')
935 return root
935 return root
936
936
937 try:
937 try:
938 homedir = env['HOME']
938 homedir = env['HOME']
939 if not isdir(homedir):
939 if not isdir(homedir):
940 # in case a user stuck some string which does NOT resolve to a
940 # in case a user stuck some string which does NOT resolve to a
941 # valid path, it's as good as if we hadn't foud it
941 # valid path, it's as good as if we hadn't foud it
942 raise KeyError
942 raise KeyError
943 return homedir
943 return homedir
944 except KeyError:
944 except KeyError:
945 if os.name == 'posix':
945 if os.name == 'posix':
946 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
946 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
947 elif os.name == 'nt':
947 elif os.name == 'nt':
948 # For some strange reason, win9x returns 'nt' for os.name.
948 # For some strange reason, win9x returns 'nt' for os.name.
949 try:
949 try:
950 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
950 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
951 if not isdir(homedir):
951 if not isdir(homedir):
952 homedir = os.path.join(env['USERPROFILE'])
952 homedir = os.path.join(env['USERPROFILE'])
953 if not isdir(homedir):
953 if not isdir(homedir):
954 raise HomeDirError
954 raise HomeDirError
955 return homedir
955 return homedir
956 except:
956 except:
957 try:
957 try:
958 # Use the registry to get the 'My Documents' folder.
958 # Use the registry to get the 'My Documents' folder.
959 import _winreg as wreg
959 import _winreg as wreg
960 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
960 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
961 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
961 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
962 homedir = wreg.QueryValueEx(key,'Personal')[0]
962 homedir = wreg.QueryValueEx(key,'Personal')[0]
963 key.Close()
963 key.Close()
964 if not isdir(homedir):
964 if not isdir(homedir):
965 e = ('Invalid "Personal" folder registry key '
965 e = ('Invalid "Personal" folder registry key '
966 'typically "My Documents".\n'
966 'typically "My Documents".\n'
967 'Value: %s\n'
967 'Value: %s\n'
968 'This is not a valid directory on your system.' %
968 'This is not a valid directory on your system.' %
969 homedir)
969 homedir)
970 raise HomeDirError(e)
970 raise HomeDirError(e)
971 return homedir
971 return homedir
972 except HomeDirError:
972 except HomeDirError:
973 raise
973 raise
974 except:
974 except:
975 return 'C:\\'
975 return 'C:\\'
976 elif os.name == 'dos':
976 elif os.name == 'dos':
977 # Desperate, may do absurd things in classic MacOS. May work under DOS.
977 # Desperate, may do absurd things in classic MacOS. May work under DOS.
978 return 'C:\\'
978 return 'C:\\'
979 else:
979 else:
980 raise HomeDirError,'support for your operating system not implemented.'
980 raise HomeDirError,'support for your operating system not implemented.'
981
981
982
983 def get_ipython_dir():
984 """Get the IPython directory for this platform and user.
985
986 This uses the logic in `get_home_dir` to find the home directory
987 and the adds either .ipython or _ipython to the end of the path.
988 """
989 if os.name == 'posix':
990 ipdir_def = '.ipython'
991 else:
992 ipdir_def = '_ipython'
993 home_dir = get_home_dir()
994 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
995 os.path.join(home_dir,ipdir_def)))
996 return ipdir
997
998 def get_security_dir():
999 """Get the IPython security directory.
1000
1001 This directory is the default location for all security related files,
1002 including SSL/TLS certificates and FURL files.
1003
1004 If the directory does not exist, it is created with 0700 permissions.
1005 If it exists, permissions are set to 0700.
1006 """
1007 security_dir = os.path.join(get_ipython_dir(), 'security')
1008 if not os.path.isdir(security_dir):
1009 os.mkdir(security_dir, 0700)
1010 else:
1011 os.chmod(security_dir, 0700)
1012 return security_dir
1013
982 #****************************************************************************
1014 #****************************************************************************
983 # strings and text
1015 # strings and text
984
1016
985 class LSString(str):
1017 class LSString(str):
986 """String derivative with a special access attributes.
1018 """String derivative with a special access attributes.
987
1019
988 These are normal strings, but with the special attributes:
1020 These are normal strings, but with the special attributes:
989
1021
990 .l (or .list) : value as list (split on newlines).
1022 .l (or .list) : value as list (split on newlines).
991 .n (or .nlstr): original value (the string itself).
1023 .n (or .nlstr): original value (the string itself).
992 .s (or .spstr): value as whitespace-separated string.
1024 .s (or .spstr): value as whitespace-separated string.
993 .p (or .paths): list of path objects
1025 .p (or .paths): list of path objects
994
1026
995 Any values which require transformations are computed only once and
1027 Any values which require transformations are computed only once and
996 cached.
1028 cached.
997
1029
998 Such strings are very useful to efficiently interact with the shell, which
1030 Such strings are very useful to efficiently interact with the shell, which
999 typically only understands whitespace-separated options for commands."""
1031 typically only understands whitespace-separated options for commands."""
1000
1032
1001 def get_list(self):
1033 def get_list(self):
1002 try:
1034 try:
1003 return self.__list
1035 return self.__list
1004 except AttributeError:
1036 except AttributeError:
1005 self.__list = self.split('\n')
1037 self.__list = self.split('\n')
1006 return self.__list
1038 return self.__list
1007
1039
1008 l = list = property(get_list)
1040 l = list = property(get_list)
1009
1041
1010 def get_spstr(self):
1042 def get_spstr(self):
1011 try:
1043 try:
1012 return self.__spstr
1044 return self.__spstr
1013 except AttributeError:
1045 except AttributeError:
1014 self.__spstr = self.replace('\n',' ')
1046 self.__spstr = self.replace('\n',' ')
1015 return self.__spstr
1047 return self.__spstr
1016
1048
1017 s = spstr = property(get_spstr)
1049 s = spstr = property(get_spstr)
1018
1050
1019 def get_nlstr(self):
1051 def get_nlstr(self):
1020 return self
1052 return self
1021
1053
1022 n = nlstr = property(get_nlstr)
1054 n = nlstr = property(get_nlstr)
1023
1055
1024 def get_paths(self):
1056 def get_paths(self):
1025 try:
1057 try:
1026 return self.__paths
1058 return self.__paths
1027 except AttributeError:
1059 except AttributeError:
1028 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1060 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1029 return self.__paths
1061 return self.__paths
1030
1062
1031 p = paths = property(get_paths)
1063 p = paths = property(get_paths)
1032
1064
1033 def print_lsstring(arg):
1065 def print_lsstring(arg):
1034 """ Prettier (non-repr-like) and more informative printer for LSString """
1066 """ Prettier (non-repr-like) and more informative printer for LSString """
1035 print "LSString (.p, .n, .l, .s available). Value:"
1067 print "LSString (.p, .n, .l, .s available). Value:"
1036 print arg
1068 print arg
1037
1069
1038 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1070 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1039
1071
1040 #----------------------------------------------------------------------------
1072 #----------------------------------------------------------------------------
1041 class SList(list):
1073 class SList(list):
1042 """List derivative with a special access attributes.
1074 """List derivative with a special access attributes.
1043
1075
1044 These are normal lists, but with the special attributes:
1076 These are normal lists, but with the special attributes:
1045
1077
1046 .l (or .list) : value as list (the list itself).
1078 .l (or .list) : value as list (the list itself).
1047 .n (or .nlstr): value as a string, joined on newlines.
1079 .n (or .nlstr): value as a string, joined on newlines.
1048 .s (or .spstr): value as a string, joined on spaces.
1080 .s (or .spstr): value as a string, joined on spaces.
1049 .p (or .paths): list of path objects
1081 .p (or .paths): list of path objects
1050
1082
1051 Any values which require transformations are computed only once and
1083 Any values which require transformations are computed only once and
1052 cached."""
1084 cached."""
1053
1085
1054 def get_list(self):
1086 def get_list(self):
1055 return self
1087 return self
1056
1088
1057 l = list = property(get_list)
1089 l = list = property(get_list)
1058
1090
1059 def get_spstr(self):
1091 def get_spstr(self):
1060 try:
1092 try:
1061 return self.__spstr
1093 return self.__spstr
1062 except AttributeError:
1094 except AttributeError:
1063 self.__spstr = ' '.join(self)
1095 self.__spstr = ' '.join(self)
1064 return self.__spstr
1096 return self.__spstr
1065
1097
1066 s = spstr = property(get_spstr)
1098 s = spstr = property(get_spstr)
1067
1099
1068 def get_nlstr(self):
1100 def get_nlstr(self):
1069 try:
1101 try:
1070 return self.__nlstr
1102 return self.__nlstr
1071 except AttributeError:
1103 except AttributeError:
1072 self.__nlstr = '\n'.join(self)
1104 self.__nlstr = '\n'.join(self)
1073 return self.__nlstr
1105 return self.__nlstr
1074
1106
1075 n = nlstr = property(get_nlstr)
1107 n = nlstr = property(get_nlstr)
1076
1108
1077 def get_paths(self):
1109 def get_paths(self):
1078 try:
1110 try:
1079 return self.__paths
1111 return self.__paths
1080 except AttributeError:
1112 except AttributeError:
1081 self.__paths = [path(p) for p in self if os.path.exists(p)]
1113 self.__paths = [path(p) for p in self if os.path.exists(p)]
1082 return self.__paths
1114 return self.__paths
1083
1115
1084 p = paths = property(get_paths)
1116 p = paths = property(get_paths)
1085
1117
1086 def grep(self, pattern, prune = False, field = None):
1118 def grep(self, pattern, prune = False, field = None):
1087 """ Return all strings matching 'pattern' (a regex or callable)
1119 """ Return all strings matching 'pattern' (a regex or callable)
1088
1120
1089 This is case-insensitive. If prune is true, return all items
1121 This is case-insensitive. If prune is true, return all items
1090 NOT matching the pattern.
1122 NOT matching the pattern.
1091
1123
1092 If field is specified, the match must occur in the specified
1124 If field is specified, the match must occur in the specified
1093 whitespace-separated field.
1125 whitespace-separated field.
1094
1126
1095 Examples::
1127 Examples::
1096
1128
1097 a.grep( lambda x: x.startswith('C') )
1129 a.grep( lambda x: x.startswith('C') )
1098 a.grep('Cha.*log', prune=1)
1130 a.grep('Cha.*log', prune=1)
1099 a.grep('chm', field=-1)
1131 a.grep('chm', field=-1)
1100 """
1132 """
1101
1133
1102 def match_target(s):
1134 def match_target(s):
1103 if field is None:
1135 if field is None:
1104 return s
1136 return s
1105 parts = s.split()
1137 parts = s.split()
1106 try:
1138 try:
1107 tgt = parts[field]
1139 tgt = parts[field]
1108 return tgt
1140 return tgt
1109 except IndexError:
1141 except IndexError:
1110 return ""
1142 return ""
1111
1143
1112 if isinstance(pattern, basestring):
1144 if isinstance(pattern, basestring):
1113 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1145 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1114 else:
1146 else:
1115 pred = pattern
1147 pred = pattern
1116 if not prune:
1148 if not prune:
1117 return SList([el for el in self if pred(match_target(el))])
1149 return SList([el for el in self if pred(match_target(el))])
1118 else:
1150 else:
1119 return SList([el for el in self if not pred(match_target(el))])
1151 return SList([el for el in self if not pred(match_target(el))])
1120 def fields(self, *fields):
1152 def fields(self, *fields):
1121 """ Collect whitespace-separated fields from string list
1153 """ Collect whitespace-separated fields from string list
1122
1154
1123 Allows quick awk-like usage of string lists.
1155 Allows quick awk-like usage of string lists.
1124
1156
1125 Example data (in var a, created by 'a = !ls -l')::
1157 Example data (in var a, created by 'a = !ls -l')::
1126 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1158 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1127 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1159 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1128
1160
1129 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1161 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1130 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1162 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1131 (note the joining by space).
1163 (note the joining by space).
1132 a.fields(-1) is ['ChangeLog', 'IPython']
1164 a.fields(-1) is ['ChangeLog', 'IPython']
1133
1165
1134 IndexErrors are ignored.
1166 IndexErrors are ignored.
1135
1167
1136 Without args, fields() just split()'s the strings.
1168 Without args, fields() just split()'s the strings.
1137 """
1169 """
1138 if len(fields) == 0:
1170 if len(fields) == 0:
1139 return [el.split() for el in self]
1171 return [el.split() for el in self]
1140
1172
1141 res = SList()
1173 res = SList()
1142 for el in [f.split() for f in self]:
1174 for el in [f.split() for f in self]:
1143 lineparts = []
1175 lineparts = []
1144
1176
1145 for fd in fields:
1177 for fd in fields:
1146 try:
1178 try:
1147 lineparts.append(el[fd])
1179 lineparts.append(el[fd])
1148 except IndexError:
1180 except IndexError:
1149 pass
1181 pass
1150 if lineparts:
1182 if lineparts:
1151 res.append(" ".join(lineparts))
1183 res.append(" ".join(lineparts))
1152
1184
1153 return res
1185 return res
1154 def sort(self,field= None, nums = False):
1186 def sort(self,field= None, nums = False):
1155 """ sort by specified fields (see fields())
1187 """ sort by specified fields (see fields())
1156
1188
1157 Example::
1189 Example::
1158 a.sort(1, nums = True)
1190 a.sort(1, nums = True)
1159
1191
1160 Sorts a by second field, in numerical order (so that 21 > 3)
1192 Sorts a by second field, in numerical order (so that 21 > 3)
1161
1193
1162 """
1194 """
1163
1195
1164 #decorate, sort, undecorate
1196 #decorate, sort, undecorate
1165 if field is not None:
1197 if field is not None:
1166 dsu = [[SList([line]).fields(field), line] for line in self]
1198 dsu = [[SList([line]).fields(field), line] for line in self]
1167 else:
1199 else:
1168 dsu = [[line, line] for line in self]
1200 dsu = [[line, line] for line in self]
1169 if nums:
1201 if nums:
1170 for i in range(len(dsu)):
1202 for i in range(len(dsu)):
1171 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1203 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1172 try:
1204 try:
1173 n = int(numstr)
1205 n = int(numstr)
1174 except ValueError:
1206 except ValueError:
1175 n = 0;
1207 n = 0;
1176 dsu[i][0] = n
1208 dsu[i][0] = n
1177
1209
1178
1210
1179 dsu.sort()
1211 dsu.sort()
1180 return SList([t[1] for t in dsu])
1212 return SList([t[1] for t in dsu])
1181
1213
1182 def print_slist(arg):
1214 def print_slist(arg):
1183 """ Prettier (non-repr-like) and more informative printer for SList """
1215 """ Prettier (non-repr-like) and more informative printer for SList """
1184 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1216 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1185 if hasattr(arg, 'hideonce') and arg.hideonce:
1217 if hasattr(arg, 'hideonce') and arg.hideonce:
1186 arg.hideonce = False
1218 arg.hideonce = False
1187 return
1219 return
1188
1220
1189 nlprint(arg)
1221 nlprint(arg)
1190
1222
1191 print_slist = result_display.when_type(SList)(print_slist)
1223 print_slist = result_display.when_type(SList)(print_slist)
1192
1224
1193
1225
1194
1226
1195 #----------------------------------------------------------------------------
1227 #----------------------------------------------------------------------------
1196 def esc_quotes(strng):
1228 def esc_quotes(strng):
1197 """Return the input string with single and double quotes escaped out"""
1229 """Return the input string with single and double quotes escaped out"""
1198
1230
1199 return strng.replace('"','\\"').replace("'","\\'")
1231 return strng.replace('"','\\"').replace("'","\\'")
1200
1232
1201 #----------------------------------------------------------------------------
1233 #----------------------------------------------------------------------------
1202 def make_quoted_expr(s):
1234 def make_quoted_expr(s):
1203 """Return string s in appropriate quotes, using raw string if possible.
1235 """Return string s in appropriate quotes, using raw string if possible.
1204
1236
1205 Effectively this turns string: cd \ao\ao\
1237 Effectively this turns string: cd \ao\ao\
1206 to: r"cd \ao\ao\_"[:-1]
1238 to: r"cd \ao\ao\_"[:-1]
1207
1239
1208 Note the use of raw string and padding at the end to allow trailing backslash.
1240 Note the use of raw string and padding at the end to allow trailing backslash.
1209
1241
1210 """
1242 """
1211
1243
1212 tail = ''
1244 tail = ''
1213 tailpadding = ''
1245 tailpadding = ''
1214 raw = ''
1246 raw = ''
1215 if "\\" in s:
1247 if "\\" in s:
1216 raw = 'r'
1248 raw = 'r'
1217 if s.endswith('\\'):
1249 if s.endswith('\\'):
1218 tail = '[:-1]'
1250 tail = '[:-1]'
1219 tailpadding = '_'
1251 tailpadding = '_'
1220 if '"' not in s:
1252 if '"' not in s:
1221 quote = '"'
1253 quote = '"'
1222 elif "'" not in s:
1254 elif "'" not in s:
1223 quote = "'"
1255 quote = "'"
1224 elif '"""' not in s and not s.endswith('"'):
1256 elif '"""' not in s and not s.endswith('"'):
1225 quote = '"""'
1257 quote = '"""'
1226 elif "'''" not in s and not s.endswith("'"):
1258 elif "'''" not in s and not s.endswith("'"):
1227 quote = "'''"
1259 quote = "'''"
1228 else:
1260 else:
1229 # give up, backslash-escaped string will do
1261 # give up, backslash-escaped string will do
1230 return '"%s"' % esc_quotes(s)
1262 return '"%s"' % esc_quotes(s)
1231 res = raw + quote + s + tailpadding + quote + tail
1263 res = raw + quote + s + tailpadding + quote + tail
1232 return res
1264 return res
1233
1265
1234
1266
1235 #----------------------------------------------------------------------------
1267 #----------------------------------------------------------------------------
1236 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1268 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1237 """Take multiple lines of input.
1269 """Take multiple lines of input.
1238
1270
1239 A list with each line of input as a separate element is returned when a
1271 A list with each line of input as a separate element is returned when a
1240 termination string is entered (defaults to a single '.'). Input can also
1272 termination string is entered (defaults to a single '.'). Input can also
1241 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1273 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1242
1274
1243 Lines of input which end in \\ are joined into single entries (and a
1275 Lines of input which end in \\ are joined into single entries (and a
1244 secondary continuation prompt is issued as long as the user terminates
1276 secondary continuation prompt is issued as long as the user terminates
1245 lines with \\). This allows entering very long strings which are still
1277 lines with \\). This allows entering very long strings which are still
1246 meant to be treated as single entities.
1278 meant to be treated as single entities.
1247 """
1279 """
1248
1280
1249 try:
1281 try:
1250 if header:
1282 if header:
1251 header += '\n'
1283 header += '\n'
1252 lines = [raw_input(header + ps1)]
1284 lines = [raw_input(header + ps1)]
1253 except EOFError:
1285 except EOFError:
1254 return []
1286 return []
1255 terminate = [terminate_str]
1287 terminate = [terminate_str]
1256 try:
1288 try:
1257 while lines[-1:] != terminate:
1289 while lines[-1:] != terminate:
1258 new_line = raw_input(ps1)
1290 new_line = raw_input(ps1)
1259 while new_line.endswith('\\'):
1291 while new_line.endswith('\\'):
1260 new_line = new_line[:-1] + raw_input(ps2)
1292 new_line = new_line[:-1] + raw_input(ps2)
1261 lines.append(new_line)
1293 lines.append(new_line)
1262
1294
1263 return lines[:-1] # don't return the termination command
1295 return lines[:-1] # don't return the termination command
1264 except EOFError:
1296 except EOFError:
1265 print
1297 print
1266 return lines
1298 return lines
1267
1299
1268 #----------------------------------------------------------------------------
1300 #----------------------------------------------------------------------------
1269 def raw_input_ext(prompt='', ps2='... '):
1301 def raw_input_ext(prompt='', ps2='... '):
1270 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1302 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1271
1303
1272 line = raw_input(prompt)
1304 line = raw_input(prompt)
1273 while line.endswith('\\'):
1305 while line.endswith('\\'):
1274 line = line[:-1] + raw_input(ps2)
1306 line = line[:-1] + raw_input(ps2)
1275 return line
1307 return line
1276
1308
1277 #----------------------------------------------------------------------------
1309 #----------------------------------------------------------------------------
1278 def ask_yes_no(prompt,default=None):
1310 def ask_yes_no(prompt,default=None):
1279 """Asks a question and returns a boolean (y/n) answer.
1311 """Asks a question and returns a boolean (y/n) answer.
1280
1312
1281 If default is given (one of 'y','n'), it is used if the user input is
1313 If default is given (one of 'y','n'), it is used if the user input is
1282 empty. Otherwise the question is repeated until an answer is given.
1314 empty. Otherwise the question is repeated until an answer is given.
1283
1315
1284 An EOF is treated as the default answer. If there is no default, an
1316 An EOF is treated as the default answer. If there is no default, an
1285 exception is raised to prevent infinite loops.
1317 exception is raised to prevent infinite loops.
1286
1318
1287 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1319 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1288
1320
1289 answers = {'y':True,'n':False,'yes':True,'no':False}
1321 answers = {'y':True,'n':False,'yes':True,'no':False}
1290 ans = None
1322 ans = None
1291 while ans not in answers.keys():
1323 while ans not in answers.keys():
1292 try:
1324 try:
1293 ans = raw_input(prompt+' ').lower()
1325 ans = raw_input(prompt+' ').lower()
1294 if not ans: # response was an empty string
1326 if not ans: # response was an empty string
1295 ans = default
1327 ans = default
1296 except KeyboardInterrupt:
1328 except KeyboardInterrupt:
1297 pass
1329 pass
1298 except EOFError:
1330 except EOFError:
1299 if default in answers.keys():
1331 if default in answers.keys():
1300 ans = default
1332 ans = default
1301 print
1333 print
1302 else:
1334 else:
1303 raise
1335 raise
1304
1336
1305 return answers[ans]
1337 return answers[ans]
1306
1338
1307 #----------------------------------------------------------------------------
1339 #----------------------------------------------------------------------------
1308 def marquee(txt='',width=78,mark='*'):
1340 def marquee(txt='',width=78,mark='*'):
1309 """Return the input string centered in a 'marquee'."""
1341 """Return the input string centered in a 'marquee'."""
1310 if not txt:
1342 if not txt:
1311 return (mark*width)[:width]
1343 return (mark*width)[:width]
1312 nmark = (width-len(txt)-2)/len(mark)/2
1344 nmark = (width-len(txt)-2)/len(mark)/2
1313 if nmark < 0: nmark =0
1345 if nmark < 0: nmark =0
1314 marks = mark*nmark
1346 marks = mark*nmark
1315 return '%s %s %s' % (marks,txt,marks)
1347 return '%s %s %s' % (marks,txt,marks)
1316
1348
1317 #----------------------------------------------------------------------------
1349 #----------------------------------------------------------------------------
1318 class EvalDict:
1350 class EvalDict:
1319 """
1351 """
1320 Emulate a dict which evaluates its contents in the caller's frame.
1352 Emulate a dict which evaluates its contents in the caller's frame.
1321
1353
1322 Usage:
1354 Usage:
1323 >>> number = 19
1355 >>> number = 19
1324
1356
1325 >>> text = "python"
1357 >>> text = "python"
1326
1358
1327 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1359 >>> print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1328 Python 2.1 rules!
1360 Python 2.1 rules!
1329 """
1361 """
1330
1362
1331 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1363 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1332 # modified (shorter) version of:
1364 # modified (shorter) version of:
1333 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1365 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1334 # Skip Montanaro (skip@pobox.com).
1366 # Skip Montanaro (skip@pobox.com).
1335
1367
1336 def __getitem__(self, name):
1368 def __getitem__(self, name):
1337 frame = sys._getframe(1)
1369 frame = sys._getframe(1)
1338 return eval(name, frame.f_globals, frame.f_locals)
1370 return eval(name, frame.f_globals, frame.f_locals)
1339
1371
1340 EvalString = EvalDict # for backwards compatibility
1372 EvalString = EvalDict # for backwards compatibility
1341 #----------------------------------------------------------------------------
1373 #----------------------------------------------------------------------------
1342 def qw(words,flat=0,sep=None,maxsplit=-1):
1374 def qw(words,flat=0,sep=None,maxsplit=-1):
1343 """Similar to Perl's qw() operator, but with some more options.
1375 """Similar to Perl's qw() operator, but with some more options.
1344
1376
1345 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1377 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1346
1378
1347 words can also be a list itself, and with flat=1, the output will be
1379 words can also be a list itself, and with flat=1, the output will be
1348 recursively flattened.
1380 recursively flattened.
1349
1381
1350 Examples:
1382 Examples:
1351
1383
1352 >>> qw('1 2')
1384 >>> qw('1 2')
1353 ['1', '2']
1385 ['1', '2']
1354
1386
1355 >>> qw(['a b','1 2',['m n','p q']])
1387 >>> qw(['a b','1 2',['m n','p q']])
1356 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1388 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1357
1389
1358 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1390 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1359 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1391 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q']
1360 """
1392 """
1361
1393
1362 if type(words) in StringTypes:
1394 if type(words) in StringTypes:
1363 return [word.strip() for word in words.split(sep,maxsplit)
1395 return [word.strip() for word in words.split(sep,maxsplit)
1364 if word and not word.isspace() ]
1396 if word and not word.isspace() ]
1365 if flat:
1397 if flat:
1366 return flatten(map(qw,words,[1]*len(words)))
1398 return flatten(map(qw,words,[1]*len(words)))
1367 return map(qw,words)
1399 return map(qw,words)
1368
1400
1369 #----------------------------------------------------------------------------
1401 #----------------------------------------------------------------------------
1370 def qwflat(words,sep=None,maxsplit=-1):
1402 def qwflat(words,sep=None,maxsplit=-1):
1371 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1403 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1372 return qw(words,1,sep,maxsplit)
1404 return qw(words,1,sep,maxsplit)
1373
1405
1374 #----------------------------------------------------------------------------
1406 #----------------------------------------------------------------------------
1375 def qw_lol(indata):
1407 def qw_lol(indata):
1376 """qw_lol('a b') -> [['a','b']],
1408 """qw_lol('a b') -> [['a','b']],
1377 otherwise it's just a call to qw().
1409 otherwise it's just a call to qw().
1378
1410
1379 We need this to make sure the modules_some keys *always* end up as a
1411 We need this to make sure the modules_some keys *always* end up as a
1380 list of lists."""
1412 list of lists."""
1381
1413
1382 if type(indata) in StringTypes:
1414 if type(indata) in StringTypes:
1383 return [qw(indata)]
1415 return [qw(indata)]
1384 else:
1416 else:
1385 return qw(indata)
1417 return qw(indata)
1386
1418
1387 #-----------------------------------------------------------------------------
1419 #-----------------------------------------------------------------------------
1388 def list_strings(arg):
1420 def list_strings(arg):
1389 """Always return a list of strings, given a string or list of strings
1421 """Always return a list of strings, given a string or list of strings
1390 as input."""
1422 as input."""
1391
1423
1392 if type(arg) in StringTypes: return [arg]
1424 if type(arg) in StringTypes: return [arg]
1393 else: return arg
1425 else: return arg
1394
1426
1395 #----------------------------------------------------------------------------
1427 #----------------------------------------------------------------------------
1396 def grep(pat,list,case=1):
1428 def grep(pat,list,case=1):
1397 """Simple minded grep-like function.
1429 """Simple minded grep-like function.
1398 grep(pat,list) returns occurrences of pat in list, None on failure.
1430 grep(pat,list) returns occurrences of pat in list, None on failure.
1399
1431
1400 It only does simple string matching, with no support for regexps. Use the
1432 It only does simple string matching, with no support for regexps. Use the
1401 option case=0 for case-insensitive matching."""
1433 option case=0 for case-insensitive matching."""
1402
1434
1403 # This is pretty crude. At least it should implement copying only references
1435 # This is pretty crude. At least it should implement copying only references
1404 # to the original data in case it's big. Now it copies the data for output.
1436 # to the original data in case it's big. Now it copies the data for output.
1405 out=[]
1437 out=[]
1406 if case:
1438 if case:
1407 for term in list:
1439 for term in list:
1408 if term.find(pat)>-1: out.append(term)
1440 if term.find(pat)>-1: out.append(term)
1409 else:
1441 else:
1410 lpat=pat.lower()
1442 lpat=pat.lower()
1411 for term in list:
1443 for term in list:
1412 if term.lower().find(lpat)>-1: out.append(term)
1444 if term.lower().find(lpat)>-1: out.append(term)
1413
1445
1414 if len(out): return out
1446 if len(out): return out
1415 else: return None
1447 else: return None
1416
1448
1417 #----------------------------------------------------------------------------
1449 #----------------------------------------------------------------------------
1418 def dgrep(pat,*opts):
1450 def dgrep(pat,*opts):
1419 """Return grep() on dir()+dir(__builtins__).
1451 """Return grep() on dir()+dir(__builtins__).
1420
1452
1421 A very common use of grep() when working interactively."""
1453 A very common use of grep() when working interactively."""
1422
1454
1423 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1455 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1424
1456
1425 #----------------------------------------------------------------------------
1457 #----------------------------------------------------------------------------
1426 def idgrep(pat):
1458 def idgrep(pat):
1427 """Case-insensitive dgrep()"""
1459 """Case-insensitive dgrep()"""
1428
1460
1429 return dgrep(pat,0)
1461 return dgrep(pat,0)
1430
1462
1431 #----------------------------------------------------------------------------
1463 #----------------------------------------------------------------------------
1432 def igrep(pat,list):
1464 def igrep(pat,list):
1433 """Synonym for case-insensitive grep."""
1465 """Synonym for case-insensitive grep."""
1434
1466
1435 return grep(pat,list,case=0)
1467 return grep(pat,list,case=0)
1436
1468
1437 #----------------------------------------------------------------------------
1469 #----------------------------------------------------------------------------
1438 def indent(str,nspaces=4,ntabs=0):
1470 def indent(str,nspaces=4,ntabs=0):
1439 """Indent a string a given number of spaces or tabstops.
1471 """Indent a string a given number of spaces or tabstops.
1440
1472
1441 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1473 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1442 """
1474 """
1443 if str is None:
1475 if str is None:
1444 return
1476 return
1445 ind = '\t'*ntabs+' '*nspaces
1477 ind = '\t'*ntabs+' '*nspaces
1446 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1478 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1447 if outstr.endswith(os.linesep+ind):
1479 if outstr.endswith(os.linesep+ind):
1448 return outstr[:-len(ind)]
1480 return outstr[:-len(ind)]
1449 else:
1481 else:
1450 return outstr
1482 return outstr
1451
1483
1452 #-----------------------------------------------------------------------------
1484 #-----------------------------------------------------------------------------
1453 def native_line_ends(filename,backup=1):
1485 def native_line_ends(filename,backup=1):
1454 """Convert (in-place) a file to line-ends native to the current OS.
1486 """Convert (in-place) a file to line-ends native to the current OS.
1455
1487
1456 If the optional backup argument is given as false, no backup of the
1488 If the optional backup argument is given as false, no backup of the
1457 original file is left. """
1489 original file is left. """
1458
1490
1459 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1491 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1460
1492
1461 bak_filename = filename + backup_suffixes[os.name]
1493 bak_filename = filename + backup_suffixes[os.name]
1462
1494
1463 original = open(filename).read()
1495 original = open(filename).read()
1464 shutil.copy2(filename,bak_filename)
1496 shutil.copy2(filename,bak_filename)
1465 try:
1497 try:
1466 new = open(filename,'wb')
1498 new = open(filename,'wb')
1467 new.write(os.linesep.join(original.splitlines()))
1499 new.write(os.linesep.join(original.splitlines()))
1468 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1500 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1469 new.close()
1501 new.close()
1470 except:
1502 except:
1471 os.rename(bak_filename,filename)
1503 os.rename(bak_filename,filename)
1472 if not backup:
1504 if not backup:
1473 try:
1505 try:
1474 os.remove(bak_filename)
1506 os.remove(bak_filename)
1475 except:
1507 except:
1476 pass
1508 pass
1477
1509
1478 #----------------------------------------------------------------------------
1510 #----------------------------------------------------------------------------
1479 def get_pager_cmd(pager_cmd = None):
1511 def get_pager_cmd(pager_cmd = None):
1480 """Return a pager command.
1512 """Return a pager command.
1481
1513
1482 Makes some attempts at finding an OS-correct one."""
1514 Makes some attempts at finding an OS-correct one."""
1483
1515
1484 if os.name == 'posix':
1516 if os.name == 'posix':
1485 default_pager_cmd = 'less -r' # -r for color control sequences
1517 default_pager_cmd = 'less -r' # -r for color control sequences
1486 elif os.name in ['nt','dos']:
1518 elif os.name in ['nt','dos']:
1487 default_pager_cmd = 'type'
1519 default_pager_cmd = 'type'
1488
1520
1489 if pager_cmd is None:
1521 if pager_cmd is None:
1490 try:
1522 try:
1491 pager_cmd = os.environ['PAGER']
1523 pager_cmd = os.environ['PAGER']
1492 except:
1524 except:
1493 pager_cmd = default_pager_cmd
1525 pager_cmd = default_pager_cmd
1494 return pager_cmd
1526 return pager_cmd
1495
1527
1496 #-----------------------------------------------------------------------------
1528 #-----------------------------------------------------------------------------
1497 def get_pager_start(pager,start):
1529 def get_pager_start(pager,start):
1498 """Return the string for paging files with an offset.
1530 """Return the string for paging files with an offset.
1499
1531
1500 This is the '+N' argument which less and more (under Unix) accept.
1532 This is the '+N' argument which less and more (under Unix) accept.
1501 """
1533 """
1502
1534
1503 if pager in ['less','more']:
1535 if pager in ['less','more']:
1504 if start:
1536 if start:
1505 start_string = '+' + str(start)
1537 start_string = '+' + str(start)
1506 else:
1538 else:
1507 start_string = ''
1539 start_string = ''
1508 else:
1540 else:
1509 start_string = ''
1541 start_string = ''
1510 return start_string
1542 return start_string
1511
1543
1512 #----------------------------------------------------------------------------
1544 #----------------------------------------------------------------------------
1513 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1545 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1514 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1546 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1515 import msvcrt
1547 import msvcrt
1516 def page_more():
1548 def page_more():
1517 """ Smart pausing between pages
1549 """ Smart pausing between pages
1518
1550
1519 @return: True if need print more lines, False if quit
1551 @return: True if need print more lines, False if quit
1520 """
1552 """
1521 Term.cout.write('---Return to continue, q to quit--- ')
1553 Term.cout.write('---Return to continue, q to quit--- ')
1522 ans = msvcrt.getch()
1554 ans = msvcrt.getch()
1523 if ans in ("q", "Q"):
1555 if ans in ("q", "Q"):
1524 result = False
1556 result = False
1525 else:
1557 else:
1526 result = True
1558 result = True
1527 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1559 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1528 return result
1560 return result
1529 else:
1561 else:
1530 def page_more():
1562 def page_more():
1531 ans = raw_input('---Return to continue, q to quit--- ')
1563 ans = raw_input('---Return to continue, q to quit--- ')
1532 if ans.lower().startswith('q'):
1564 if ans.lower().startswith('q'):
1533 return False
1565 return False
1534 else:
1566 else:
1535 return True
1567 return True
1536
1568
1537 esc_re = re.compile(r"(\x1b[^m]+m)")
1569 esc_re = re.compile(r"(\x1b[^m]+m)")
1538
1570
1539 def page_dumb(strng,start=0,screen_lines=25):
1571 def page_dumb(strng,start=0,screen_lines=25):
1540 """Very dumb 'pager' in Python, for when nothing else works.
1572 """Very dumb 'pager' in Python, for when nothing else works.
1541
1573
1542 Only moves forward, same interface as page(), except for pager_cmd and
1574 Only moves forward, same interface as page(), except for pager_cmd and
1543 mode."""
1575 mode."""
1544
1576
1545 out_ln = strng.splitlines()[start:]
1577 out_ln = strng.splitlines()[start:]
1546 screens = chop(out_ln,screen_lines-1)
1578 screens = chop(out_ln,screen_lines-1)
1547 if len(screens) == 1:
1579 if len(screens) == 1:
1548 print >>Term.cout, os.linesep.join(screens[0])
1580 print >>Term.cout, os.linesep.join(screens[0])
1549 else:
1581 else:
1550 last_escape = ""
1582 last_escape = ""
1551 for scr in screens[0:-1]:
1583 for scr in screens[0:-1]:
1552 hunk = os.linesep.join(scr)
1584 hunk = os.linesep.join(scr)
1553 print >>Term.cout, last_escape + hunk
1585 print >>Term.cout, last_escape + hunk
1554 if not page_more():
1586 if not page_more():
1555 return
1587 return
1556 esc_list = esc_re.findall(hunk)
1588 esc_list = esc_re.findall(hunk)
1557 if len(esc_list) > 0:
1589 if len(esc_list) > 0:
1558 last_escape = esc_list[-1]
1590 last_escape = esc_list[-1]
1559 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1591 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1560
1592
1561 #----------------------------------------------------------------------------
1593 #----------------------------------------------------------------------------
1562 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1594 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1563 """Print a string, piping through a pager after a certain length.
1595 """Print a string, piping through a pager after a certain length.
1564
1596
1565 The screen_lines parameter specifies the number of *usable* lines of your
1597 The screen_lines parameter specifies the number of *usable* lines of your
1566 terminal screen (total lines minus lines you need to reserve to show other
1598 terminal screen (total lines minus lines you need to reserve to show other
1567 information).
1599 information).
1568
1600
1569 If you set screen_lines to a number <=0, page() will try to auto-determine
1601 If you set screen_lines to a number <=0, page() will try to auto-determine
1570 your screen size and will only use up to (screen_size+screen_lines) for
1602 your screen size and will only use up to (screen_size+screen_lines) for
1571 printing, paging after that. That is, if you want auto-detection but need
1603 printing, paging after that. That is, if you want auto-detection but need
1572 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1604 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1573 auto-detection without any lines reserved simply use screen_lines = 0.
1605 auto-detection without any lines reserved simply use screen_lines = 0.
1574
1606
1575 If a string won't fit in the allowed lines, it is sent through the
1607 If a string won't fit in the allowed lines, it is sent through the
1576 specified pager command. If none given, look for PAGER in the environment,
1608 specified pager command. If none given, look for PAGER in the environment,
1577 and ultimately default to less.
1609 and ultimately default to less.
1578
1610
1579 If no system pager works, the string is sent through a 'dumb pager'
1611 If no system pager works, the string is sent through a 'dumb pager'
1580 written in python, very simplistic.
1612 written in python, very simplistic.
1581 """
1613 """
1582
1614
1583 # Some routines may auto-compute start offsets incorrectly and pass a
1615 # Some routines may auto-compute start offsets incorrectly and pass a
1584 # negative value. Offset to 0 for robustness.
1616 # negative value. Offset to 0 for robustness.
1585 start = max(0,start)
1617 start = max(0,start)
1586
1618
1587 # first, try the hook
1619 # first, try the hook
1588 ip = IPython.ipapi.get()
1620 ip = IPython.ipapi.get()
1589 if ip:
1621 if ip:
1590 try:
1622 try:
1591 ip.IP.hooks.show_in_pager(strng)
1623 ip.IP.hooks.show_in_pager(strng)
1592 return
1624 return
1593 except IPython.ipapi.TryNext:
1625 except IPython.ipapi.TryNext:
1594 pass
1626 pass
1595
1627
1596 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1628 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1597 TERM = os.environ.get('TERM','dumb')
1629 TERM = os.environ.get('TERM','dumb')
1598 if TERM in ['dumb','emacs'] and os.name != 'nt':
1630 if TERM in ['dumb','emacs'] and os.name != 'nt':
1599 print strng
1631 print strng
1600 return
1632 return
1601 # chop off the topmost part of the string we don't want to see
1633 # chop off the topmost part of the string we don't want to see
1602 str_lines = strng.split(os.linesep)[start:]
1634 str_lines = strng.split(os.linesep)[start:]
1603 str_toprint = os.linesep.join(str_lines)
1635 str_toprint = os.linesep.join(str_lines)
1604 num_newlines = len(str_lines)
1636 num_newlines = len(str_lines)
1605 len_str = len(str_toprint)
1637 len_str = len(str_toprint)
1606
1638
1607 # Dumb heuristics to guesstimate number of on-screen lines the string
1639 # Dumb heuristics to guesstimate number of on-screen lines the string
1608 # takes. Very basic, but good enough for docstrings in reasonable
1640 # takes. Very basic, but good enough for docstrings in reasonable
1609 # terminals. If someone later feels like refining it, it's not hard.
1641 # terminals. If someone later feels like refining it, it's not hard.
1610 numlines = max(num_newlines,int(len_str/80)+1)
1642 numlines = max(num_newlines,int(len_str/80)+1)
1611
1643
1612 if os.name == "nt":
1644 if os.name == "nt":
1613 screen_lines_def = get_console_size(defaulty=25)[1]
1645 screen_lines_def = get_console_size(defaulty=25)[1]
1614 else:
1646 else:
1615 screen_lines_def = 25 # default value if we can't auto-determine
1647 screen_lines_def = 25 # default value if we can't auto-determine
1616
1648
1617 # auto-determine screen size
1649 # auto-determine screen size
1618 if screen_lines <= 0:
1650 if screen_lines <= 0:
1619 if TERM=='xterm':
1651 if TERM=='xterm':
1620 use_curses = USE_CURSES
1652 use_curses = USE_CURSES
1621 else:
1653 else:
1622 # curses causes problems on many terminals other than xterm.
1654 # curses causes problems on many terminals other than xterm.
1623 use_curses = False
1655 use_curses = False
1624 if use_curses:
1656 if use_curses:
1625 # There is a bug in curses, where *sometimes* it fails to properly
1657 # There is a bug in curses, where *sometimes* it fails to properly
1626 # initialize, and then after the endwin() call is made, the
1658 # initialize, and then after the endwin() call is made, the
1627 # terminal is left in an unusable state. Rather than trying to
1659 # terminal is left in an unusable state. Rather than trying to
1628 # check everytime for this (by requesting and comparing termios
1660 # check everytime for this (by requesting and comparing termios
1629 # flags each time), we just save the initial terminal state and
1661 # flags each time), we just save the initial terminal state and
1630 # unconditionally reset it every time. It's cheaper than making
1662 # unconditionally reset it every time. It's cheaper than making
1631 # the checks.
1663 # the checks.
1632 term_flags = termios.tcgetattr(sys.stdout)
1664 term_flags = termios.tcgetattr(sys.stdout)
1633 scr = curses.initscr()
1665 scr = curses.initscr()
1634 screen_lines_real,screen_cols = scr.getmaxyx()
1666 screen_lines_real,screen_cols = scr.getmaxyx()
1635 curses.endwin()
1667 curses.endwin()
1636 # Restore terminal state in case endwin() didn't.
1668 # Restore terminal state in case endwin() didn't.
1637 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1669 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1638 # Now we have what we needed: the screen size in rows/columns
1670 # Now we have what we needed: the screen size in rows/columns
1639 screen_lines += screen_lines_real
1671 screen_lines += screen_lines_real
1640 #print '***Screen size:',screen_lines_real,'lines x',\
1672 #print '***Screen size:',screen_lines_real,'lines x',\
1641 #screen_cols,'columns.' # dbg
1673 #screen_cols,'columns.' # dbg
1642 else:
1674 else:
1643 screen_lines += screen_lines_def
1675 screen_lines += screen_lines_def
1644
1676
1645 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1677 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1646 if numlines <= screen_lines :
1678 if numlines <= screen_lines :
1647 #print '*** normal print' # dbg
1679 #print '*** normal print' # dbg
1648 print >>Term.cout, str_toprint
1680 print >>Term.cout, str_toprint
1649 else:
1681 else:
1650 # Try to open pager and default to internal one if that fails.
1682 # Try to open pager and default to internal one if that fails.
1651 # All failure modes are tagged as 'retval=1', to match the return
1683 # All failure modes are tagged as 'retval=1', to match the return
1652 # value of a failed system command. If any intermediate attempt
1684 # value of a failed system command. If any intermediate attempt
1653 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1685 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1654 pager_cmd = get_pager_cmd(pager_cmd)
1686 pager_cmd = get_pager_cmd(pager_cmd)
1655 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1687 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1656 if os.name == 'nt':
1688 if os.name == 'nt':
1657 if pager_cmd.startswith('type'):
1689 if pager_cmd.startswith('type'):
1658 # The default WinXP 'type' command is failing on complex strings.
1690 # The default WinXP 'type' command is failing on complex strings.
1659 retval = 1
1691 retval = 1
1660 else:
1692 else:
1661 tmpname = tempfile.mktemp('.txt')
1693 tmpname = tempfile.mktemp('.txt')
1662 tmpfile = file(tmpname,'wt')
1694 tmpfile = file(tmpname,'wt')
1663 tmpfile.write(strng)
1695 tmpfile.write(strng)
1664 tmpfile.close()
1696 tmpfile.close()
1665 cmd = "%s < %s" % (pager_cmd,tmpname)
1697 cmd = "%s < %s" % (pager_cmd,tmpname)
1666 if os.system(cmd):
1698 if os.system(cmd):
1667 retval = 1
1699 retval = 1
1668 else:
1700 else:
1669 retval = None
1701 retval = None
1670 os.remove(tmpname)
1702 os.remove(tmpname)
1671 else:
1703 else:
1672 try:
1704 try:
1673 retval = None
1705 retval = None
1674 # if I use popen4, things hang. No idea why.
1706 # if I use popen4, things hang. No idea why.
1675 #pager,shell_out = os.popen4(pager_cmd)
1707 #pager,shell_out = os.popen4(pager_cmd)
1676 pager = os.popen(pager_cmd,'w')
1708 pager = os.popen(pager_cmd,'w')
1677 pager.write(strng)
1709 pager.write(strng)
1678 pager.close()
1710 pager.close()
1679 retval = pager.close() # success returns None
1711 retval = pager.close() # success returns None
1680 except IOError,msg: # broken pipe when user quits
1712 except IOError,msg: # broken pipe when user quits
1681 if msg.args == (32,'Broken pipe'):
1713 if msg.args == (32,'Broken pipe'):
1682 retval = None
1714 retval = None
1683 else:
1715 else:
1684 retval = 1
1716 retval = 1
1685 except OSError:
1717 except OSError:
1686 # Other strange problems, sometimes seen in Win2k/cygwin
1718 # Other strange problems, sometimes seen in Win2k/cygwin
1687 retval = 1
1719 retval = 1
1688 if retval is not None:
1720 if retval is not None:
1689 page_dumb(strng,screen_lines=screen_lines)
1721 page_dumb(strng,screen_lines=screen_lines)
1690
1722
1691 #----------------------------------------------------------------------------
1723 #----------------------------------------------------------------------------
1692 def page_file(fname,start = 0, pager_cmd = None):
1724 def page_file(fname,start = 0, pager_cmd = None):
1693 """Page a file, using an optional pager command and starting line.
1725 """Page a file, using an optional pager command and starting line.
1694 """
1726 """
1695
1727
1696 pager_cmd = get_pager_cmd(pager_cmd)
1728 pager_cmd = get_pager_cmd(pager_cmd)
1697 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1729 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1698
1730
1699 try:
1731 try:
1700 if os.environ['TERM'] in ['emacs','dumb']:
1732 if os.environ['TERM'] in ['emacs','dumb']:
1701 raise EnvironmentError
1733 raise EnvironmentError
1702 xsys(pager_cmd + ' ' + fname)
1734 xsys(pager_cmd + ' ' + fname)
1703 except:
1735 except:
1704 try:
1736 try:
1705 if start > 0:
1737 if start > 0:
1706 start -= 1
1738 start -= 1
1707 page(open(fname).read(),start)
1739 page(open(fname).read(),start)
1708 except:
1740 except:
1709 print 'Unable to show file',`fname`
1741 print 'Unable to show file',`fname`
1710
1742
1711
1743
1712 #----------------------------------------------------------------------------
1744 #----------------------------------------------------------------------------
1713 def snip_print(str,width = 75,print_full = 0,header = ''):
1745 def snip_print(str,width = 75,print_full = 0,header = ''):
1714 """Print a string snipping the midsection to fit in width.
1746 """Print a string snipping the midsection to fit in width.
1715
1747
1716 print_full: mode control:
1748 print_full: mode control:
1717 - 0: only snip long strings
1749 - 0: only snip long strings
1718 - 1: send to page() directly.
1750 - 1: send to page() directly.
1719 - 2: snip long strings and ask for full length viewing with page()
1751 - 2: snip long strings and ask for full length viewing with page()
1720 Return 1 if snipping was necessary, 0 otherwise."""
1752 Return 1 if snipping was necessary, 0 otherwise."""
1721
1753
1722 if print_full == 1:
1754 if print_full == 1:
1723 page(header+str)
1755 page(header+str)
1724 return 0
1756 return 0
1725
1757
1726 print header,
1758 print header,
1727 if len(str) < width:
1759 if len(str) < width:
1728 print str
1760 print str
1729 snip = 0
1761 snip = 0
1730 else:
1762 else:
1731 whalf = int((width -5)/2)
1763 whalf = int((width -5)/2)
1732 print str[:whalf] + ' <...> ' + str[-whalf:]
1764 print str[:whalf] + ' <...> ' + str[-whalf:]
1733 snip = 1
1765 snip = 1
1734 if snip and print_full == 2:
1766 if snip and print_full == 2:
1735 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1767 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1736 page(str)
1768 page(str)
1737 return snip
1769 return snip
1738
1770
1739 #****************************************************************************
1771 #****************************************************************************
1740 # lists, dicts and structures
1772 # lists, dicts and structures
1741
1773
1742 def belong(candidates,checklist):
1774 def belong(candidates,checklist):
1743 """Check whether a list of items appear in a given list of options.
1775 """Check whether a list of items appear in a given list of options.
1744
1776
1745 Returns a list of 1 and 0, one for each candidate given."""
1777 Returns a list of 1 and 0, one for each candidate given."""
1746
1778
1747 return [x in checklist for x in candidates]
1779 return [x in checklist for x in candidates]
1748
1780
1749 #----------------------------------------------------------------------------
1781 #----------------------------------------------------------------------------
1750 def uniq_stable(elems):
1782 def uniq_stable(elems):
1751 """uniq_stable(elems) -> list
1783 """uniq_stable(elems) -> list
1752
1784
1753 Return from an iterable, a list of all the unique elements in the input,
1785 Return from an iterable, a list of all the unique elements in the input,
1754 but maintaining the order in which they first appear.
1786 but maintaining the order in which they first appear.
1755
1787
1756 A naive solution to this problem which just makes a dictionary with the
1788 A naive solution to this problem which just makes a dictionary with the
1757 elements as keys fails to respect the stability condition, since
1789 elements as keys fails to respect the stability condition, since
1758 dictionaries are unsorted by nature.
1790 dictionaries are unsorted by nature.
1759
1791
1760 Note: All elements in the input must be valid dictionary keys for this
1792 Note: All elements in the input must be valid dictionary keys for this
1761 routine to work, as it internally uses a dictionary for efficiency
1793 routine to work, as it internally uses a dictionary for efficiency
1762 reasons."""
1794 reasons."""
1763
1795
1764 unique = []
1796 unique = []
1765 unique_dict = {}
1797 unique_dict = {}
1766 for nn in elems:
1798 for nn in elems:
1767 if nn not in unique_dict:
1799 if nn not in unique_dict:
1768 unique.append(nn)
1800 unique.append(nn)
1769 unique_dict[nn] = None
1801 unique_dict[nn] = None
1770 return unique
1802 return unique
1771
1803
1772 #----------------------------------------------------------------------------
1804 #----------------------------------------------------------------------------
1773 class NLprinter:
1805 class NLprinter:
1774 """Print an arbitrarily nested list, indicating index numbers.
1806 """Print an arbitrarily nested list, indicating index numbers.
1775
1807
1776 An instance of this class called nlprint is available and callable as a
1808 An instance of this class called nlprint is available and callable as a
1777 function.
1809 function.
1778
1810
1779 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1811 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1780 and using 'sep' to separate the index from the value. """
1812 and using 'sep' to separate the index from the value. """
1781
1813
1782 def __init__(self):
1814 def __init__(self):
1783 self.depth = 0
1815 self.depth = 0
1784
1816
1785 def __call__(self,lst,pos='',**kw):
1817 def __call__(self,lst,pos='',**kw):
1786 """Prints the nested list numbering levels."""
1818 """Prints the nested list numbering levels."""
1787 kw.setdefault('indent',' ')
1819 kw.setdefault('indent',' ')
1788 kw.setdefault('sep',': ')
1820 kw.setdefault('sep',': ')
1789 kw.setdefault('start',0)
1821 kw.setdefault('start',0)
1790 kw.setdefault('stop',len(lst))
1822 kw.setdefault('stop',len(lst))
1791 # we need to remove start and stop from kw so they don't propagate
1823 # we need to remove start and stop from kw so they don't propagate
1792 # into a recursive call for a nested list.
1824 # into a recursive call for a nested list.
1793 start = kw['start']; del kw['start']
1825 start = kw['start']; del kw['start']
1794 stop = kw['stop']; del kw['stop']
1826 stop = kw['stop']; del kw['stop']
1795 if self.depth == 0 and 'header' in kw.keys():
1827 if self.depth == 0 and 'header' in kw.keys():
1796 print kw['header']
1828 print kw['header']
1797
1829
1798 for idx in range(start,stop):
1830 for idx in range(start,stop):
1799 elem = lst[idx]
1831 elem = lst[idx]
1800 if type(elem)==type([]):
1832 if type(elem)==type([]):
1801 self.depth += 1
1833 self.depth += 1
1802 self.__call__(elem,itpl('$pos$idx,'),**kw)
1834 self.__call__(elem,itpl('$pos$idx,'),**kw)
1803 self.depth -= 1
1835 self.depth -= 1
1804 else:
1836 else:
1805 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1837 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1806
1838
1807 nlprint = NLprinter()
1839 nlprint = NLprinter()
1808 #----------------------------------------------------------------------------
1840 #----------------------------------------------------------------------------
1809 def all_belong(candidates,checklist):
1841 def all_belong(candidates,checklist):
1810 """Check whether a list of items ALL appear in a given list of options.
1842 """Check whether a list of items ALL appear in a given list of options.
1811
1843
1812 Returns a single 1 or 0 value."""
1844 Returns a single 1 or 0 value."""
1813
1845
1814 return 1-(0 in [x in checklist for x in candidates])
1846 return 1-(0 in [x in checklist for x in candidates])
1815
1847
1816 #----------------------------------------------------------------------------
1848 #----------------------------------------------------------------------------
1817 def sort_compare(lst1,lst2,inplace = 1):
1849 def sort_compare(lst1,lst2,inplace = 1):
1818 """Sort and compare two lists.
1850 """Sort and compare two lists.
1819
1851
1820 By default it does it in place, thus modifying the lists. Use inplace = 0
1852 By default it does it in place, thus modifying the lists. Use inplace = 0
1821 to avoid that (at the cost of temporary copy creation)."""
1853 to avoid that (at the cost of temporary copy creation)."""
1822 if not inplace:
1854 if not inplace:
1823 lst1 = lst1[:]
1855 lst1 = lst1[:]
1824 lst2 = lst2[:]
1856 lst2 = lst2[:]
1825 lst1.sort(); lst2.sort()
1857 lst1.sort(); lst2.sort()
1826 return lst1 == lst2
1858 return lst1 == lst2
1827
1859
1828 #----------------------------------------------------------------------------
1860 #----------------------------------------------------------------------------
1829 def list2dict(lst):
1861 def list2dict(lst):
1830 """Takes a list of (key,value) pairs and turns it into a dict."""
1862 """Takes a list of (key,value) pairs and turns it into a dict."""
1831
1863
1832 dic = {}
1864 dic = {}
1833 for k,v in lst: dic[k] = v
1865 for k,v in lst: dic[k] = v
1834 return dic
1866 return dic
1835
1867
1836 #----------------------------------------------------------------------------
1868 #----------------------------------------------------------------------------
1837 def list2dict2(lst,default=''):
1869 def list2dict2(lst,default=''):
1838 """Takes a list and turns it into a dict.
1870 """Takes a list and turns it into a dict.
1839 Much slower than list2dict, but more versatile. This version can take
1871 Much slower than list2dict, but more versatile. This version can take
1840 lists with sublists of arbitrary length (including sclars)."""
1872 lists with sublists of arbitrary length (including sclars)."""
1841
1873
1842 dic = {}
1874 dic = {}
1843 for elem in lst:
1875 for elem in lst:
1844 if type(elem) in (types.ListType,types.TupleType):
1876 if type(elem) in (types.ListType,types.TupleType):
1845 size = len(elem)
1877 size = len(elem)
1846 if size == 0:
1878 if size == 0:
1847 pass
1879 pass
1848 elif size == 1:
1880 elif size == 1:
1849 dic[elem] = default
1881 dic[elem] = default
1850 else:
1882 else:
1851 k,v = elem[0], elem[1:]
1883 k,v = elem[0], elem[1:]
1852 if len(v) == 1: v = v[0]
1884 if len(v) == 1: v = v[0]
1853 dic[k] = v
1885 dic[k] = v
1854 else:
1886 else:
1855 dic[elem] = default
1887 dic[elem] = default
1856 return dic
1888 return dic
1857
1889
1858 #----------------------------------------------------------------------------
1890 #----------------------------------------------------------------------------
1859 def flatten(seq):
1891 def flatten(seq):
1860 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1892 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1861
1893
1862 return [x for subseq in seq for x in subseq]
1894 return [x for subseq in seq for x in subseq]
1863
1895
1864 #----------------------------------------------------------------------------
1896 #----------------------------------------------------------------------------
1865 def get_slice(seq,start=0,stop=None,step=1):
1897 def get_slice(seq,start=0,stop=None,step=1):
1866 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1898 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1867 if stop == None:
1899 if stop == None:
1868 stop = len(seq)
1900 stop = len(seq)
1869 item = lambda i: seq[i]
1901 item = lambda i: seq[i]
1870 return map(item,xrange(start,stop,step))
1902 return map(item,xrange(start,stop,step))
1871
1903
1872 #----------------------------------------------------------------------------
1904 #----------------------------------------------------------------------------
1873 def chop(seq,size):
1905 def chop(seq,size):
1874 """Chop a sequence into chunks of the given size."""
1906 """Chop a sequence into chunks of the given size."""
1875 chunk = lambda i: seq[i:i+size]
1907 chunk = lambda i: seq[i:i+size]
1876 return map(chunk,xrange(0,len(seq),size))
1908 return map(chunk,xrange(0,len(seq),size))
1877
1909
1878 #----------------------------------------------------------------------------
1910 #----------------------------------------------------------------------------
1879 # with is a keyword as of python 2.5, so this function is renamed to withobj
1911 # with is a keyword as of python 2.5, so this function is renamed to withobj
1880 # from its old 'with' name.
1912 # from its old 'with' name.
1881 def with_obj(object, **args):
1913 def with_obj(object, **args):
1882 """Set multiple attributes for an object, similar to Pascal's with.
1914 """Set multiple attributes for an object, similar to Pascal's with.
1883
1915
1884 Example:
1916 Example:
1885 with_obj(jim,
1917 with_obj(jim,
1886 born = 1960,
1918 born = 1960,
1887 haircolour = 'Brown',
1919 haircolour = 'Brown',
1888 eyecolour = 'Green')
1920 eyecolour = 'Green')
1889
1921
1890 Credit: Greg Ewing, in
1922 Credit: Greg Ewing, in
1891 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1923 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1892
1924
1893 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1925 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1894 has become a keyword for Python 2.5, so we had to rename it."""
1926 has become a keyword for Python 2.5, so we had to rename it."""
1895
1927
1896 object.__dict__.update(args)
1928 object.__dict__.update(args)
1897
1929
1898 #----------------------------------------------------------------------------
1930 #----------------------------------------------------------------------------
1899 def setattr_list(obj,alist,nspace = None):
1931 def setattr_list(obj,alist,nspace = None):
1900 """Set a list of attributes for an object taken from a namespace.
1932 """Set a list of attributes for an object taken from a namespace.
1901
1933
1902 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1934 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1903 alist with their values taken from nspace, which must be a dict (something
1935 alist with their values taken from nspace, which must be a dict (something
1904 like locals() will often do) If nspace isn't given, locals() of the
1936 like locals() will often do) If nspace isn't given, locals() of the
1905 *caller* is used, so in most cases you can omit it.
1937 *caller* is used, so in most cases you can omit it.
1906
1938
1907 Note that alist can be given as a string, which will be automatically
1939 Note that alist can be given as a string, which will be automatically
1908 split into a list on whitespace. If given as a list, it must be a list of
1940 split into a list on whitespace. If given as a list, it must be a list of
1909 *strings* (the variable names themselves), not of variables."""
1941 *strings* (the variable names themselves), not of variables."""
1910
1942
1911 # this grabs the local variables from the *previous* call frame -- that is
1943 # this grabs the local variables from the *previous* call frame -- that is
1912 # the locals from the function that called setattr_list().
1944 # the locals from the function that called setattr_list().
1913 # - snipped from weave.inline()
1945 # - snipped from weave.inline()
1914 if nspace is None:
1946 if nspace is None:
1915 call_frame = sys._getframe().f_back
1947 call_frame = sys._getframe().f_back
1916 nspace = call_frame.f_locals
1948 nspace = call_frame.f_locals
1917
1949
1918 if type(alist) in StringTypes:
1950 if type(alist) in StringTypes:
1919 alist = alist.split()
1951 alist = alist.split()
1920 for attr in alist:
1952 for attr in alist:
1921 val = eval(attr,nspace)
1953 val = eval(attr,nspace)
1922 setattr(obj,attr,val)
1954 setattr(obj,attr,val)
1923
1955
1924 #----------------------------------------------------------------------------
1956 #----------------------------------------------------------------------------
1925 def getattr_list(obj,alist,*args):
1957 def getattr_list(obj,alist,*args):
1926 """getattr_list(obj,alist[, default]) -> attribute list.
1958 """getattr_list(obj,alist[, default]) -> attribute list.
1927
1959
1928 Get a list of named attributes for an object. When a default argument is
1960 Get a list of named attributes for an object. When a default argument is
1929 given, it is returned when the attribute doesn't exist; without it, an
1961 given, it is returned when the attribute doesn't exist; without it, an
1930 exception is raised in that case.
1962 exception is raised in that case.
1931
1963
1932 Note that alist can be given as a string, which will be automatically
1964 Note that alist can be given as a string, which will be automatically
1933 split into a list on whitespace. If given as a list, it must be a list of
1965 split into a list on whitespace. If given as a list, it must be a list of
1934 *strings* (the variable names themselves), not of variables."""
1966 *strings* (the variable names themselves), not of variables."""
1935
1967
1936 if type(alist) in StringTypes:
1968 if type(alist) in StringTypes:
1937 alist = alist.split()
1969 alist = alist.split()
1938 if args:
1970 if args:
1939 if len(args)==1:
1971 if len(args)==1:
1940 default = args[0]
1972 default = args[0]
1941 return map(lambda attr: getattr(obj,attr,default),alist)
1973 return map(lambda attr: getattr(obj,attr,default),alist)
1942 else:
1974 else:
1943 raise ValueError,'getattr_list() takes only one optional argument'
1975 raise ValueError,'getattr_list() takes only one optional argument'
1944 else:
1976 else:
1945 return map(lambda attr: getattr(obj,attr),alist)
1977 return map(lambda attr: getattr(obj,attr),alist)
1946
1978
1947 #----------------------------------------------------------------------------
1979 #----------------------------------------------------------------------------
1948 def map_method(method,object_list,*argseq,**kw):
1980 def map_method(method,object_list,*argseq,**kw):
1949 """map_method(method,object_list,*args,**kw) -> list
1981 """map_method(method,object_list,*args,**kw) -> list
1950
1982
1951 Return a list of the results of applying the methods to the items of the
1983 Return a list of the results of applying the methods to the items of the
1952 argument sequence(s). If more than one sequence is given, the method is
1984 argument sequence(s). If more than one sequence is given, the method is
1953 called with an argument list consisting of the corresponding item of each
1985 called with an argument list consisting of the corresponding item of each
1954 sequence. All sequences must be of the same length.
1986 sequence. All sequences must be of the same length.
1955
1987
1956 Keyword arguments are passed verbatim to all objects called.
1988 Keyword arguments are passed verbatim to all objects called.
1957
1989
1958 This is Python code, so it's not nearly as fast as the builtin map()."""
1990 This is Python code, so it's not nearly as fast as the builtin map()."""
1959
1991
1960 out_list = []
1992 out_list = []
1961 idx = 0
1993 idx = 0
1962 for object in object_list:
1994 for object in object_list:
1963 try:
1995 try:
1964 handler = getattr(object, method)
1996 handler = getattr(object, method)
1965 except AttributeError:
1997 except AttributeError:
1966 out_list.append(None)
1998 out_list.append(None)
1967 else:
1999 else:
1968 if argseq:
2000 if argseq:
1969 args = map(lambda lst:lst[idx],argseq)
2001 args = map(lambda lst:lst[idx],argseq)
1970 #print 'ob',object,'hand',handler,'ar',args # dbg
2002 #print 'ob',object,'hand',handler,'ar',args # dbg
1971 out_list.append(handler(args,**kw))
2003 out_list.append(handler(args,**kw))
1972 else:
2004 else:
1973 out_list.append(handler(**kw))
2005 out_list.append(handler(**kw))
1974 idx += 1
2006 idx += 1
1975 return out_list
2007 return out_list
1976
2008
1977 #----------------------------------------------------------------------------
2009 #----------------------------------------------------------------------------
1978 def get_class_members(cls):
2010 def get_class_members(cls):
1979 ret = dir(cls)
2011 ret = dir(cls)
1980 if hasattr(cls,'__bases__'):
2012 if hasattr(cls,'__bases__'):
1981 for base in cls.__bases__:
2013 for base in cls.__bases__:
1982 ret.extend(get_class_members(base))
2014 ret.extend(get_class_members(base))
1983 return ret
2015 return ret
1984
2016
1985 #----------------------------------------------------------------------------
2017 #----------------------------------------------------------------------------
1986 def dir2(obj):
2018 def dir2(obj):
1987 """dir2(obj) -> list of strings
2019 """dir2(obj) -> list of strings
1988
2020
1989 Extended version of the Python builtin dir(), which does a few extra
2021 Extended version of the Python builtin dir(), which does a few extra
1990 checks, and supports common objects with unusual internals that confuse
2022 checks, and supports common objects with unusual internals that confuse
1991 dir(), such as Traits and PyCrust.
2023 dir(), such as Traits and PyCrust.
1992
2024
1993 This version is guaranteed to return only a list of true strings, whereas
2025 This version is guaranteed to return only a list of true strings, whereas
1994 dir() returns anything that objects inject into themselves, even if they
2026 dir() returns anything that objects inject into themselves, even if they
1995 are later not really valid for attribute access (many extension libraries
2027 are later not really valid for attribute access (many extension libraries
1996 have such bugs).
2028 have such bugs).
1997 """
2029 """
1998
2030
1999 # Start building the attribute list via dir(), and then complete it
2031 # Start building the attribute list via dir(), and then complete it
2000 # with a few extra special-purpose calls.
2032 # with a few extra special-purpose calls.
2001 words = dir(obj)
2033 words = dir(obj)
2002
2034
2003 if hasattr(obj,'__class__'):
2035 if hasattr(obj,'__class__'):
2004 words.append('__class__')
2036 words.append('__class__')
2005 words.extend(get_class_members(obj.__class__))
2037 words.extend(get_class_members(obj.__class__))
2006 #if '__base__' in words: 1/0
2038 #if '__base__' in words: 1/0
2007
2039
2008 # Some libraries (such as traits) may introduce duplicates, we want to
2040 # Some libraries (such as traits) may introduce duplicates, we want to
2009 # track and clean this up if it happens
2041 # track and clean this up if it happens
2010 may_have_dupes = False
2042 may_have_dupes = False
2011
2043
2012 # this is the 'dir' function for objects with Enthought's traits
2044 # this is the 'dir' function for objects with Enthought's traits
2013 if hasattr(obj, 'trait_names'):
2045 if hasattr(obj, 'trait_names'):
2014 try:
2046 try:
2015 words.extend(obj.trait_names())
2047 words.extend(obj.trait_names())
2016 may_have_dupes = True
2048 may_have_dupes = True
2017 except TypeError:
2049 except TypeError:
2018 # This will happen if `obj` is a class and not an instance.
2050 # This will happen if `obj` is a class and not an instance.
2019 pass
2051 pass
2020
2052
2021 # Support for PyCrust-style _getAttributeNames magic method.
2053 # Support for PyCrust-style _getAttributeNames magic method.
2022 if hasattr(obj, '_getAttributeNames'):
2054 if hasattr(obj, '_getAttributeNames'):
2023 try:
2055 try:
2024 words.extend(obj._getAttributeNames())
2056 words.extend(obj._getAttributeNames())
2025 may_have_dupes = True
2057 may_have_dupes = True
2026 except TypeError:
2058 except TypeError:
2027 # `obj` is a class and not an instance. Ignore
2059 # `obj` is a class and not an instance. Ignore
2028 # this error.
2060 # this error.
2029 pass
2061 pass
2030
2062
2031 if may_have_dupes:
2063 if may_have_dupes:
2032 # eliminate possible duplicates, as some traits may also
2064 # eliminate possible duplicates, as some traits may also
2033 # appear as normal attributes in the dir() call.
2065 # appear as normal attributes in the dir() call.
2034 words = list(set(words))
2066 words = list(set(words))
2035 words.sort()
2067 words.sort()
2036
2068
2037 # filter out non-string attributes which may be stuffed by dir() calls
2069 # filter out non-string attributes which may be stuffed by dir() calls
2038 # and poor coding in third-party modules
2070 # and poor coding in third-party modules
2039 return [w for w in words if isinstance(w, basestring)]
2071 return [w for w in words if isinstance(w, basestring)]
2040
2072
2041 #----------------------------------------------------------------------------
2073 #----------------------------------------------------------------------------
2042 def import_fail_info(mod_name,fns=None):
2074 def import_fail_info(mod_name,fns=None):
2043 """Inform load failure for a module."""
2075 """Inform load failure for a module."""
2044
2076
2045 if fns == None:
2077 if fns == None:
2046 warn("Loading of %s failed.\n" % (mod_name,))
2078 warn("Loading of %s failed.\n" % (mod_name,))
2047 else:
2079 else:
2048 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2080 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2049
2081
2050 #----------------------------------------------------------------------------
2082 #----------------------------------------------------------------------------
2051 # Proposed popitem() extension, written as a method
2083 # Proposed popitem() extension, written as a method
2052
2084
2053
2085
2054 class NotGiven: pass
2086 class NotGiven: pass
2055
2087
2056 def popkey(dct,key,default=NotGiven):
2088 def popkey(dct,key,default=NotGiven):
2057 """Return dct[key] and delete dct[key].
2089 """Return dct[key] and delete dct[key].
2058
2090
2059 If default is given, return it if dct[key] doesn't exist, otherwise raise
2091 If default is given, return it if dct[key] doesn't exist, otherwise raise
2060 KeyError. """
2092 KeyError. """
2061
2093
2062 try:
2094 try:
2063 val = dct[key]
2095 val = dct[key]
2064 except KeyError:
2096 except KeyError:
2065 if default is NotGiven:
2097 if default is NotGiven:
2066 raise
2098 raise
2067 else:
2099 else:
2068 return default
2100 return default
2069 else:
2101 else:
2070 del dct[key]
2102 del dct[key]
2071 return val
2103 return val
2072
2104
2073 def wrap_deprecated(func, suggest = '<nothing>'):
2105 def wrap_deprecated(func, suggest = '<nothing>'):
2074 def newFunc(*args, **kwargs):
2106 def newFunc(*args, **kwargs):
2075 warnings.warn("Call to deprecated function %s, use %s instead" %
2107 warnings.warn("Call to deprecated function %s, use %s instead" %
2076 ( func.__name__, suggest),
2108 ( func.__name__, suggest),
2077 category=DeprecationWarning,
2109 category=DeprecationWarning,
2078 stacklevel = 2)
2110 stacklevel = 2)
2079 return func(*args, **kwargs)
2111 return func(*args, **kwargs)
2080 return newFunc
2112 return newFunc
2081
2113
2082
2114
2083 def _num_cpus_unix():
2115 def _num_cpus_unix():
2084 """Return the number of active CPUs on a Unix system."""
2116 """Return the number of active CPUs on a Unix system."""
2085 return os.sysconf("SC_NPROCESSORS_ONLN")
2117 return os.sysconf("SC_NPROCESSORS_ONLN")
2086
2118
2087
2119
2088 def _num_cpus_darwin():
2120 def _num_cpus_darwin():
2089 """Return the number of active CPUs on a Darwin system."""
2121 """Return the number of active CPUs on a Darwin system."""
2090 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2122 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2091 return p.stdout.read()
2123 return p.stdout.read()
2092
2124
2093
2125
2094 def _num_cpus_windows():
2126 def _num_cpus_windows():
2095 """Return the number of active CPUs on a Windows system."""
2127 """Return the number of active CPUs on a Windows system."""
2096 return os.environ.get("NUMBER_OF_PROCESSORS")
2128 return os.environ.get("NUMBER_OF_PROCESSORS")
2097
2129
2098
2130
2099 def num_cpus():
2131 def num_cpus():
2100 """Return the effective number of CPUs in the system as an integer.
2132 """Return the effective number of CPUs in the system as an integer.
2101
2133
2102 This cross-platform function makes an attempt at finding the total number of
2134 This cross-platform function makes an attempt at finding the total number of
2103 available CPUs in the system, as returned by various underlying system and
2135 available CPUs in the system, as returned by various underlying system and
2104 python calls.
2136 python calls.
2105
2137
2106 If it can't find a sensible answer, it returns 1 (though an error *may* make
2138 If it can't find a sensible answer, it returns 1 (though an error *may* make
2107 it return a large positive number that's actually incorrect).
2139 it return a large positive number that's actually incorrect).
2108 """
2140 """
2109
2141
2110 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2142 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2111 # for the names of the keys we needed to look up for this function. This
2143 # for the names of the keys we needed to look up for this function. This
2112 # code was inspired by their equivalent function.
2144 # code was inspired by their equivalent function.
2113
2145
2114 ncpufuncs = {'Linux':_num_cpus_unix,
2146 ncpufuncs = {'Linux':_num_cpus_unix,
2115 'Darwin':_num_cpus_darwin,
2147 'Darwin':_num_cpus_darwin,
2116 'Windows':_num_cpus_windows,
2148 'Windows':_num_cpus_windows,
2117 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2149 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2118 # See http://bugs.python.org/issue1082 for details.
2150 # See http://bugs.python.org/issue1082 for details.
2119 'Microsoft':_num_cpus_windows,
2151 'Microsoft':_num_cpus_windows,
2120 }
2152 }
2121
2153
2122 ncpufunc = ncpufuncs.get(platform.system(),
2154 ncpufunc = ncpufuncs.get(platform.system(),
2123 # default to unix version (Solaris, AIX, etc)
2155 # default to unix version (Solaris, AIX, etc)
2124 _num_cpus_unix)
2156 _num_cpus_unix)
2125
2157
2126 try:
2158 try:
2127 ncpus = max(1,int(ncpufunc()))
2159 ncpus = max(1,int(ncpufunc()))
2128 except:
2160 except:
2129 ncpus = 1
2161 ncpus = 1
2130 return ncpus
2162 return ncpus
2131
2163
2132 #*************************** end of file <genutils.py> **********************
2164 #*************************** end of file <genutils.py> **********************
@@ -1,125 +1,123 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 from os.path import join as pjoin
19
18 from IPython.external.configobj import ConfigObj
20 from IPython.external.configobj import ConfigObj
19 from IPython.config.api import ConfigObjManager
21 from IPython.config.api import ConfigObjManager
20 from IPython.config.cutils import get_ipython_dir
22 from IPython.genutils import get_ipython_dir, get_security_dir
21
23
22 default_kernel_config = ConfigObj()
24 default_kernel_config = ConfigObj()
23
25
24 try:
26 security_dir = get_security_dir()
25 ipython_dir = get_ipython_dir() + '/'
26 except:
27 # This will defaults to the cwd
28 ipython_dir = ''
29
27
30 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
31 # Engine Configuration
29 # Engine Configuration
32 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
33
31
34 engine_config = dict(
32 engine_config = dict(
35 logfile = '', # Empty means log to stdout
33 logfile = '', # Empty means log to stdout
36 furl_file = ipython_dir + 'ipcontroller-engine.furl'
34 furl_file = pjoin(security_dir, 'ipcontroller-engine.furl')
37 )
35 )
38
36
39 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
40 # MPI Configuration
38 # MPI Configuration
41 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
42
40
43 mpi_config = dict(
41 mpi_config = dict(
44 mpi4py = """from mpi4py import MPI as mpi
42 mpi4py = """from mpi4py import MPI as mpi
45 mpi.size = mpi.COMM_WORLD.Get_size()
43 mpi.size = mpi.COMM_WORLD.Get_size()
46 mpi.rank = mpi.COMM_WORLD.Get_rank()
44 mpi.rank = mpi.COMM_WORLD.Get_rank()
47 """,
45 """,
48 pytrilinos = """from PyTrilinos import Epetra
46 pytrilinos = """from PyTrilinos import Epetra
49 class SimpleStruct:
47 class SimpleStruct:
50 pass
48 pass
51 mpi = SimpleStruct()
49 mpi = SimpleStruct()
52 mpi.rank = 0
50 mpi.rank = 0
53 mpi.size = 0
51 mpi.size = 0
54 """,
52 """,
55 default = ''
53 default = ''
56 )
54 )
57
55
58 #-------------------------------------------------------------------------------
56 #-------------------------------------------------------------------------------
59 # Controller Configuration
57 # Controller Configuration
60 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
61
59
62 controller_config = dict(
60 controller_config = dict(
63
61
64 logfile = '', # Empty means log to stdout
62 logfile = '', # Empty means log to stdout
65 import_statement = '',
63 import_statement = '',
66
64
67 engine_tub = dict(
65 engine_tub = dict(
68 ip = '', # Empty string means all interfaces
66 ip = '', # Empty string means all interfaces
69 port = 0, # 0 means pick a port for me
67 port = 0, # 0 means pick a port for me
70 location = '', # Empty string means try to set automatically
68 location = '', # Empty string means try to set automatically
71 secure = True,
69 secure = True,
72 cert_file = ipython_dir + 'ipcontroller-engine.pem',
70 cert_file = pjoin(security_dir, 'ipcontroller-engine.pem'),
73 ),
71 ),
74 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
72 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
75 engine_furl_file = ipython_dir + 'ipcontroller-engine.furl',
73 engine_furl_file = pjoin(security_dir, 'ipcontroller-engine.furl'),
76
74
77 controller_interfaces = dict(
75 controller_interfaces = dict(
78 # multiengine = dict(
76 # multiengine = dict(
79 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
77 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
80 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
78 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
81 # furl_file = 'ipcontroller-mec.furl'
79 # furl_file = 'ipcontroller-mec.furl'
82 # ),
80 # ),
83 task = dict(
81 task = dict(
84 controller_interface = 'IPython.kernel.task.ITaskController',
82 controller_interface = 'IPython.kernel.task.ITaskController',
85 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
83 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
86 furl_file = ipython_dir + 'ipcontroller-tc.furl'
84 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
87 ),
85 ),
88 multiengine = dict(
86 multiengine = dict(
89 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
87 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
90 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
88 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
91 furl_file = ipython_dir + 'ipcontroller-mec.furl'
89 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
92 )
90 )
93 ),
91 ),
94
92
95 client_tub = dict(
93 client_tub = dict(
96 ip = '', # Empty string means all interfaces
94 ip = '', # Empty string means all interfaces
97 port = 0, # 0 means pick a port for me
95 port = 0, # 0 means pick a port for me
98 location = '', # Empty string means try to set automatically
96 location = '', # Empty string means try to set automatically
99 secure = True,
97 secure = True,
100 cert_file = ipython_dir + 'ipcontroller-client.pem'
98 cert_file = pjoin(security_dir, 'ipcontroller-client.pem')
101 )
99 )
102 )
100 )
103
101
104 #-------------------------------------------------------------------------------
102 #-------------------------------------------------------------------------------
105 # Client Configuration
103 # Client Configuration
106 #-------------------------------------------------------------------------------
104 #-------------------------------------------------------------------------------
107
105
108 client_config = dict(
106 client_config = dict(
109 client_interfaces = dict(
107 client_interfaces = dict(
110 task = dict(
108 task = dict(
111 furl_file = ipython_dir + 'ipcontroller-tc.furl'
109 furl_file = pjoin(security_dir, 'ipcontroller-tc.furl')
112 ),
110 ),
113 multiengine = dict(
111 multiengine = dict(
114 furl_file = ipython_dir + 'ipcontroller-mec.furl'
112 furl_file = pjoin(security_dir, 'ipcontroller-mec.furl')
115 )
113 )
116 )
114 )
117 )
115 )
118
116
119 default_kernel_config['engine'] = engine_config
117 default_kernel_config['engine'] = engine_config
120 default_kernel_config['mpi'] = mpi_config
118 default_kernel_config['mpi'] = mpi_config
121 default_kernel_config['controller'] = controller_config
119 default_kernel_config['controller'] = controller_config
122 default_kernel_config['client'] = client_config
120 default_kernel_config['client'] = client_config
123
121
124
122
125 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini') No newline at end of file
123 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini')
@@ -1,376 +1,376 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_controllerservice -*-
2 # -*- test-case-name: IPython.kernel.test.test_controllerservice -*-
3
3
4 """A Twisted Service for the IPython Controller.
4 """A Twisted Service for the IPython Controller.
5
5
6 The IPython Controller:
6 The IPython Controller:
7
7
8 * Listens for Engines to connect and then manages access to those engines.
8 * Listens for Engines to connect and then manages access to those engines.
9 * Listens for clients and passes commands from client to the Engines.
9 * Listens for clients and passes commands from client to the Engines.
10 * Exposes an asynchronous interfaces to the Engines which themselves can block.
10 * Exposes an asynchronous interfaces to the Engines which themselves can block.
11 * Acts as a gateway to the Engines.
11 * Acts as a gateway to the Engines.
12
12
13 The design of the controller is somewhat abstract to allow flexibility in how
13 The design of the controller is somewhat abstract to allow flexibility in how
14 the controller is presented to clients. This idea is that there is a basic
14 the controller is presented to clients. This idea is that there is a basic
15 ControllerService class that allows engines to connect to it. But, this
15 ControllerService class that allows engines to connect to it. But, this
16 basic class has no client interfaces. To expose client interfaces developers
16 basic class has no client interfaces. To expose client interfaces developers
17 provide an adapter that makes the ControllerService look like something. For
17 provide an adapter that makes the ControllerService look like something. For
18 example, one client interface might support task farming and another might
18 example, one client interface might support task farming and another might
19 support interactive usage. The important thing is that by using interfaces
19 support interactive usage. The important thing is that by using interfaces
20 and adapters, a single controller can be accessed from multiple interfaces.
20 and adapters, a single controller can be accessed from multiple interfaces.
21 Furthermore, by adapting various client interfaces to various network
21 Furthermore, by adapting various client interfaces to various network
22 protocols, each client interface can be exposed to multiple network protocols.
22 protocols, each client interface can be exposed to multiple network protocols.
23 See multiengine.py for an example of how to adapt the ControllerService
23 See multiengine.py for an example of how to adapt the ControllerService
24 to a client interface.
24 to a client interface.
25 """
25 """
26
26
27 __docformat__ = "restructuredtext en"
27 __docformat__ = "restructuredtext en"
28
28
29 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
30 # Copyright (C) 2008 The IPython Development Team
30 # Copyright (C) 2008 The IPython Development Team
31 #
31 #
32 # Distributed under the terms of the BSD License. The full license is in
32 # Distributed under the terms of the BSD License. The full license is in
33 # the file COPYING, distributed as part of this software.
33 # the file COPYING, distributed as part of this software.
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
37 # Imports
37 # Imports
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39
39
40 import os, sys
40 import os, sys
41
41
42 from twisted.application import service
42 from twisted.application import service
43 from twisted.internet import defer, reactor
43 from twisted.internet import defer, reactor
44 from twisted.python import log, components
44 from twisted.python import log, components
45 from zope.interface import Interface, implements, Attribute
45 from zope.interface import Interface, implements, Attribute
46 import zope.interface as zi
46 import zope.interface as zi
47
47
48 from IPython.kernel.engineservice import \
48 from IPython.kernel.engineservice import \
49 IEngineCore, \
49 IEngineCore, \
50 IEngineSerialized, \
50 IEngineSerialized, \
51 IEngineQueued
51 IEngineQueued
52
52
53 from IPython.config import cutils
53 from IPython.genutils import get_ipython_dir
54 from IPython.kernel import codeutil
54 from IPython.kernel import codeutil
55
55
56 #-------------------------------------------------------------------------------
56 #-------------------------------------------------------------------------------
57 # Interfaces for the Controller
57 # Interfaces for the Controller
58 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
59
59
60 class IControllerCore(Interface):
60 class IControllerCore(Interface):
61 """Basic methods any controller must have.
61 """Basic methods any controller must have.
62
62
63 This is basically the aspect of the controller relevant to the
63 This is basically the aspect of the controller relevant to the
64 engines and does not assume anything about how the engines will
64 engines and does not assume anything about how the engines will
65 be presented to a client.
65 be presented to a client.
66 """
66 """
67
67
68 engines = Attribute("A dict of engine ids and engine instances.")
68 engines = Attribute("A dict of engine ids and engine instances.")
69
69
70 def register_engine(remoteEngine, id=None, ip=None, port=None,
70 def register_engine(remoteEngine, id=None, ip=None, port=None,
71 pid=None):
71 pid=None):
72 """Register new remote engine.
72 """Register new remote engine.
73
73
74 The controller can use the ip, port, pid of the engine to do useful things
74 The controller can use the ip, port, pid of the engine to do useful things
75 like kill the engines.
75 like kill the engines.
76
76
77 :Parameters:
77 :Parameters:
78 remoteEngine
78 remoteEngine
79 An implementer of IEngineCore, IEngineSerialized and IEngineQueued.
79 An implementer of IEngineCore, IEngineSerialized and IEngineQueued.
80 id : int
80 id : int
81 Requested id.
81 Requested id.
82 ip : str
82 ip : str
83 IP address the engine is running on.
83 IP address the engine is running on.
84 port : int
84 port : int
85 Port the engine is on.
85 Port the engine is on.
86 pid : int
86 pid : int
87 pid of the running engine.
87 pid of the running engine.
88
88
89 :Returns: A dict of {'id':id} and possibly other key, value pairs.
89 :Returns: A dict of {'id':id} and possibly other key, value pairs.
90 """
90 """
91
91
92 def unregister_engine(id):
92 def unregister_engine(id):
93 """Handle a disconnecting engine.
93 """Handle a disconnecting engine.
94
94
95 :Parameters:
95 :Parameters:
96 id
96 id
97 The integer engine id of the engine to unregister.
97 The integer engine id of the engine to unregister.
98 """
98 """
99
99
100 def on_register_engine_do(f, includeID, *args, **kwargs):
100 def on_register_engine_do(f, includeID, *args, **kwargs):
101 """Call ``f(*args, **kwargs)`` when an engine is registered.
101 """Call ``f(*args, **kwargs)`` when an engine is registered.
102
102
103 :Parameters:
103 :Parameters:
104 includeID : int
104 includeID : int
105 If True the first argument to f will be the id of the engine.
105 If True the first argument to f will be the id of the engine.
106 """
106 """
107
107
108 def on_unregister_engine_do(f, includeID, *args, **kwargs):
108 def on_unregister_engine_do(f, includeID, *args, **kwargs):
109 """Call ``f(*args, **kwargs)`` when an engine is unregistered.
109 """Call ``f(*args, **kwargs)`` when an engine is unregistered.
110
110
111 :Parameters:
111 :Parameters:
112 includeID : int
112 includeID : int
113 If True the first argument to f will be the id of the engine.
113 If True the first argument to f will be the id of the engine.
114 """
114 """
115
115
116 def on_register_engine_do_not(f):
116 def on_register_engine_do_not(f):
117 """Stop calling f on engine registration"""
117 """Stop calling f on engine registration"""
118
118
119 def on_unregister_engine_do_not(f):
119 def on_unregister_engine_do_not(f):
120 """Stop calling f on engine unregistration"""
120 """Stop calling f on engine unregistration"""
121
121
122 def on_n_engines_registered_do(n, f, *arg, **kwargs):
122 def on_n_engines_registered_do(n, f, *arg, **kwargs):
123 """Call f(*args, **kwargs) the first time the nth engine registers."""
123 """Call f(*args, **kwargs) the first time the nth engine registers."""
124
124
125 class IControllerBase(IControllerCore):
125 class IControllerBase(IControllerCore):
126 """The basic controller interface."""
126 """The basic controller interface."""
127 pass
127 pass
128
128
129
129
130 #-------------------------------------------------------------------------------
130 #-------------------------------------------------------------------------------
131 # Implementation of the ControllerService
131 # Implementation of the ControllerService
132 #-------------------------------------------------------------------------------
132 #-------------------------------------------------------------------------------
133
133
134 class ControllerService(object, service.Service):
134 class ControllerService(object, service.Service):
135 """A basic Controller represented as a Twisted Service.
135 """A basic Controller represented as a Twisted Service.
136
136
137 This class doesn't implement any client notification mechanism. That
137 This class doesn't implement any client notification mechanism. That
138 is up to adapted subclasses.
138 is up to adapted subclasses.
139 """
139 """
140
140
141 # I also pick up the IService interface by inheritance from service.Service
141 # I also pick up the IService interface by inheritance from service.Service
142 implements(IControllerBase)
142 implements(IControllerBase)
143 name = 'ControllerService'
143 name = 'ControllerService'
144
144
145 def __init__(self, maxEngines=511, saveIDs=False):
145 def __init__(self, maxEngines=511, saveIDs=False):
146 self.saveIDs = saveIDs
146 self.saveIDs = saveIDs
147 self.engines = {}
147 self.engines = {}
148 self.availableIDs = range(maxEngines,-1,-1) # [511,...,0]
148 self.availableIDs = range(maxEngines,-1,-1) # [511,...,0]
149 self._onRegister = []
149 self._onRegister = []
150 self._onUnregister = []
150 self._onUnregister = []
151 self._onNRegistered = []
151 self._onNRegistered = []
152
152
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154 # Methods used to save the engine info to a log file
154 # Methods used to save the engine info to a log file
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156
156
157 def _buildEngineInfoString(self, id, ip, port, pid):
157 def _buildEngineInfoString(self, id, ip, port, pid):
158 if id is None:
158 if id is None:
159 id = -99
159 id = -99
160 if ip is None:
160 if ip is None:
161 ip = "-99"
161 ip = "-99"
162 if port is None:
162 if port is None:
163 port = -99
163 port = -99
164 if pid is None:
164 if pid is None:
165 pid = -99
165 pid = -99
166 return "Engine Info: %d %s %d %d" % (id, ip , port, pid)
166 return "Engine Info: %d %s %d %d" % (id, ip , port, pid)
167
167
168 def _logEngineInfo(self, id, ip, port, pid):
168 def _logEngineInfo(self, id, ip, port, pid):
169 log.msg(self._buildEngineInfoString(id,ip,port,pid))
169 log.msg(self._buildEngineInfoString(id,ip,port,pid))
170
170
171 def _getEngineInfoLogFile(self):
171 def _getEngineInfoLogFile(self):
172 # Store all logs inside the ipython directory
172 # Store all logs inside the ipython directory
173 ipdir = cutils.get_ipython_dir()
173 ipdir = get_ipython_dir()
174 pjoin = os.path.join
174 pjoin = os.path.join
175 logdir_base = pjoin(ipdir,'log')
175 logdir_base = pjoin(ipdir,'log')
176 if not os.path.isdir(logdir_base):
176 if not os.path.isdir(logdir_base):
177 os.makedirs(logdir_base)
177 os.makedirs(logdir_base)
178 logfile = os.path.join(logdir_base,'ipcontroller-%s-engine-info.log' % os.getpid())
178 logfile = os.path.join(logdir_base,'ipcontroller-%s-engine-info.log' % os.getpid())
179 return logfile
179 return logfile
180
180
181 def _logEngineInfoToFile(self, id, ip, port, pid):
181 def _logEngineInfoToFile(self, id, ip, port, pid):
182 """Log info about an engine to a log file.
182 """Log info about an engine to a log file.
183
183
184 When an engine registers with a ControllerService, the ControllerService
184 When an engine registers with a ControllerService, the ControllerService
185 saves information about the engine to a log file. That information
185 saves information about the engine to a log file. That information
186 can be useful for various purposes, such as killing hung engines, etc.
186 can be useful for various purposes, such as killing hung engines, etc.
187
187
188 This method takes the assigned id, ip/port and pid of the engine
188 This method takes the assigned id, ip/port and pid of the engine
189 and saves it to a file of the form:
189 and saves it to a file of the form:
190
190
191 ~/.ipython/log/ipcontroller-###-engine-info.log
191 ~/.ipython/log/ipcontroller-###-engine-info.log
192
192
193 where ### is the pid of the controller.
193 where ### is the pid of the controller.
194
194
195 Each line of this file has the form:
195 Each line of this file has the form:
196
196
197 Engine Info: ip ip port pid
197 Engine Info: ip ip port pid
198
198
199 If any of the entries are not known, they are replaced by -99.
199 If any of the entries are not known, they are replaced by -99.
200 """
200 """
201
201
202 fname = self._getEngineInfoLogFile()
202 fname = self._getEngineInfoLogFile()
203 f = open(fname, 'a')
203 f = open(fname, 'a')
204 s = self._buildEngineInfoString(id,ip,port,pid)
204 s = self._buildEngineInfoString(id,ip,port,pid)
205 f.write(s + '\n')
205 f.write(s + '\n')
206 f.close()
206 f.close()
207
207
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209 # IControllerCore methods
209 # IControllerCore methods
210 #---------------------------------------------------------------------------
210 #---------------------------------------------------------------------------
211
211
212 def register_engine(self, remoteEngine, id=None,
212 def register_engine(self, remoteEngine, id=None,
213 ip=None, port=None, pid=None):
213 ip=None, port=None, pid=None):
214 """Register new engine connection"""
214 """Register new engine connection"""
215
215
216 # What happens if these assertions fail?
216 # What happens if these assertions fail?
217 assert IEngineCore.providedBy(remoteEngine), \
217 assert IEngineCore.providedBy(remoteEngine), \
218 "engine passed to register_engine doesn't provide IEngineCore"
218 "engine passed to register_engine doesn't provide IEngineCore"
219 assert IEngineSerialized.providedBy(remoteEngine), \
219 assert IEngineSerialized.providedBy(remoteEngine), \
220 "engine passed to register_engine doesn't provide IEngineSerialized"
220 "engine passed to register_engine doesn't provide IEngineSerialized"
221 assert IEngineQueued.providedBy(remoteEngine), \
221 assert IEngineQueued.providedBy(remoteEngine), \
222 "engine passed to register_engine doesn't provide IEngineQueued"
222 "engine passed to register_engine doesn't provide IEngineQueued"
223 assert isinstance(id, int) or id is None, \
223 assert isinstance(id, int) or id is None, \
224 "id to register_engine must be an integer or None"
224 "id to register_engine must be an integer or None"
225 assert isinstance(ip, str) or ip is None, \
225 assert isinstance(ip, str) or ip is None, \
226 "ip to register_engine must be a string or None"
226 "ip to register_engine must be a string or None"
227 assert isinstance(port, int) or port is None, \
227 assert isinstance(port, int) or port is None, \
228 "port to register_engine must be an integer or None"
228 "port to register_engine must be an integer or None"
229 assert isinstance(pid, int) or pid is None, \
229 assert isinstance(pid, int) or pid is None, \
230 "pid to register_engine must be an integer or None"
230 "pid to register_engine must be an integer or None"
231
231
232 desiredID = id
232 desiredID = id
233 if desiredID in self.engines.keys():
233 if desiredID in self.engines.keys():
234 desiredID = None
234 desiredID = None
235
235
236 if desiredID in self.availableIDs:
236 if desiredID in self.availableIDs:
237 getID = desiredID
237 getID = desiredID
238 self.availableIDs.remove(desiredID)
238 self.availableIDs.remove(desiredID)
239 else:
239 else:
240 getID = self.availableIDs.pop()
240 getID = self.availableIDs.pop()
241 remoteEngine.id = getID
241 remoteEngine.id = getID
242 remoteEngine.service = self
242 remoteEngine.service = self
243 self.engines[getID] = remoteEngine
243 self.engines[getID] = remoteEngine
244
244
245 # Log the Engine Information for monitoring purposes
245 # Log the Engine Information for monitoring purposes
246 self._logEngineInfoToFile(getID, ip, port, pid)
246 self._logEngineInfoToFile(getID, ip, port, pid)
247
247
248 msg = "registered engine with id: %i" %getID
248 msg = "registered engine with id: %i" %getID
249 log.msg(msg)
249 log.msg(msg)
250
250
251 for i in range(len(self._onRegister)):
251 for i in range(len(self._onRegister)):
252 (f,args,kwargs,ifid) = self._onRegister[i]
252 (f,args,kwargs,ifid) = self._onRegister[i]
253 try:
253 try:
254 if ifid:
254 if ifid:
255 f(getID, *args, **kwargs)
255 f(getID, *args, **kwargs)
256 else:
256 else:
257 f(*args, **kwargs)
257 f(*args, **kwargs)
258 except:
258 except:
259 self._onRegister.pop(i)
259 self._onRegister.pop(i)
260
260
261 # Call functions when the nth engine is registered and them remove them
261 # Call functions when the nth engine is registered and them remove them
262 for i, (n, f, args, kwargs) in enumerate(self._onNRegistered):
262 for i, (n, f, args, kwargs) in enumerate(self._onNRegistered):
263 if len(self.engines.keys()) == n:
263 if len(self.engines.keys()) == n:
264 try:
264 try:
265 try:
265 try:
266 f(*args, **kwargs)
266 f(*args, **kwargs)
267 except:
267 except:
268 log.msg("Function %r failed when the %ith engine registered" % (f, n))
268 log.msg("Function %r failed when the %ith engine registered" % (f, n))
269 finally:
269 finally:
270 self._onNRegistered.pop(i)
270 self._onNRegistered.pop(i)
271
271
272 return {'id':getID}
272 return {'id':getID}
273
273
274 def unregister_engine(self, id):
274 def unregister_engine(self, id):
275 """Unregister engine by id."""
275 """Unregister engine by id."""
276
276
277 assert isinstance(id, int) or id is None, \
277 assert isinstance(id, int) or id is None, \
278 "id to unregister_engine must be an integer or None"
278 "id to unregister_engine must be an integer or None"
279
279
280 msg = "unregistered engine with id: %i" %id
280 msg = "unregistered engine with id: %i" %id
281 log.msg(msg)
281 log.msg(msg)
282 try:
282 try:
283 del self.engines[id]
283 del self.engines[id]
284 except KeyError:
284 except KeyError:
285 log.msg("engine with id %i was not registered" % id)
285 log.msg("engine with id %i was not registered" % id)
286 else:
286 else:
287 if not self.saveIDs:
287 if not self.saveIDs:
288 self.availableIDs.append(id)
288 self.availableIDs.append(id)
289 # Sort to assign lower ids first
289 # Sort to assign lower ids first
290 self.availableIDs.sort(reverse=True)
290 self.availableIDs.sort(reverse=True)
291 else:
291 else:
292 log.msg("preserving id %i" %id)
292 log.msg("preserving id %i" %id)
293
293
294 for i in range(len(self._onUnregister)):
294 for i in range(len(self._onUnregister)):
295 (f,args,kwargs,ifid) = self._onUnregister[i]
295 (f,args,kwargs,ifid) = self._onUnregister[i]
296 try:
296 try:
297 if ifid:
297 if ifid:
298 f(id, *args, **kwargs)
298 f(id, *args, **kwargs)
299 else:
299 else:
300 f(*args, **kwargs)
300 f(*args, **kwargs)
301 except:
301 except:
302 self._onUnregister.pop(i)
302 self._onUnregister.pop(i)
303
303
304 def on_register_engine_do(self, f, includeID, *args, **kwargs):
304 def on_register_engine_do(self, f, includeID, *args, **kwargs):
305 assert callable(f), "f must be callable"
305 assert callable(f), "f must be callable"
306 self._onRegister.append((f,args,kwargs,includeID))
306 self._onRegister.append((f,args,kwargs,includeID))
307
307
308 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
308 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
309 assert callable(f), "f must be callable"
309 assert callable(f), "f must be callable"
310 self._onUnregister.append((f,args,kwargs,includeID))
310 self._onUnregister.append((f,args,kwargs,includeID))
311
311
312 def on_register_engine_do_not(self, f):
312 def on_register_engine_do_not(self, f):
313 for i in range(len(self._onRegister)):
313 for i in range(len(self._onRegister)):
314 g = self._onRegister[i][0]
314 g = self._onRegister[i][0]
315 if f == g:
315 if f == g:
316 self._onRegister.pop(i)
316 self._onRegister.pop(i)
317 return
317 return
318
318
319 def on_unregister_engine_do_not(self, f):
319 def on_unregister_engine_do_not(self, f):
320 for i in range(len(self._onUnregister)):
320 for i in range(len(self._onUnregister)):
321 g = self._onUnregister[i][0]
321 g = self._onUnregister[i][0]
322 if f == g:
322 if f == g:
323 self._onUnregister.pop(i)
323 self._onUnregister.pop(i)
324 return
324 return
325
325
326 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
326 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
327 if len(self.engines.keys()) >= n:
327 if len(self.engines.keys()) >= n:
328 f(*args, **kwargs)
328 f(*args, **kwargs)
329 else:
329 else:
330 self._onNRegistered.append((n,f,args,kwargs))
330 self._onNRegistered.append((n,f,args,kwargs))
331
331
332
332
333 #-------------------------------------------------------------------------------
333 #-------------------------------------------------------------------------------
334 # Base class for adapting controller to different client APIs
334 # Base class for adapting controller to different client APIs
335 #-------------------------------------------------------------------------------
335 #-------------------------------------------------------------------------------
336
336
337 class ControllerAdapterBase(object):
337 class ControllerAdapterBase(object):
338 """All Controller adapters should inherit from this class.
338 """All Controller adapters should inherit from this class.
339
339
340 This class provides a wrapped version of the IControllerBase interface that
340 This class provides a wrapped version of the IControllerBase interface that
341 can be used to easily create new custom controllers. Subclasses of this
341 can be used to easily create new custom controllers. Subclasses of this
342 will provide a full implementation of IControllerBase.
342 will provide a full implementation of IControllerBase.
343
343
344 This class doesn't implement any client notification mechanism. That
344 This class doesn't implement any client notification mechanism. That
345 is up to subclasses.
345 is up to subclasses.
346 """
346 """
347
347
348 implements(IControllerBase)
348 implements(IControllerBase)
349
349
350 def __init__(self, controller):
350 def __init__(self, controller):
351 self.controller = controller
351 self.controller = controller
352 # Needed for IControllerCore
352 # Needed for IControllerCore
353 self.engines = self.controller.engines
353 self.engines = self.controller.engines
354
354
355 def register_engine(self, remoteEngine, id=None,
355 def register_engine(self, remoteEngine, id=None,
356 ip=None, port=None, pid=None):
356 ip=None, port=None, pid=None):
357 return self.controller.register_engine(remoteEngine,
357 return self.controller.register_engine(remoteEngine,
358 id, ip, port, pid)
358 id, ip, port, pid)
359
359
360 def unregister_engine(self, id):
360 def unregister_engine(self, id):
361 return self.controller.unregister_engine(id)
361 return self.controller.unregister_engine(id)
362
362
363 def on_register_engine_do(self, f, includeID, *args, **kwargs):
363 def on_register_engine_do(self, f, includeID, *args, **kwargs):
364 return self.controller.on_register_engine_do(f, includeID, *args, **kwargs)
364 return self.controller.on_register_engine_do(f, includeID, *args, **kwargs)
365
365
366 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
366 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
367 return self.controller.on_unregister_engine_do(f, includeID, *args, **kwargs)
367 return self.controller.on_unregister_engine_do(f, includeID, *args, **kwargs)
368
368
369 def on_register_engine_do_not(self, f):
369 def on_register_engine_do_not(self, f):
370 return self.controller.on_register_engine_do_not(f)
370 return self.controller.on_register_engine_do_not(f)
371
371
372 def on_unregister_engine_do_not(self, f):
372 def on_unregister_engine_do_not(self, f):
373 return self.controller.on_unregister_engine_do_not(f)
373 return self.controller.on_unregister_engine_do_not(f)
374
374
375 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
375 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
376 return self.controller.on_n_engines_registered_do(n, f, *args, **kwargs)
376 return self.controller.on_n_engines_registered_do(n, f, *args, **kwargs)
@@ -1,747 +1,754 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Central interpreter object for an IPython engine.
3 """Central interpreter object for an IPython engine.
4
4
5 The interpreter is the object whose job is to process lines of user input and
5 The interpreter is the object whose job is to process lines of user input and
6 actually execute them in the user's namespace.
6 actually execute them in the user's namespace.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 # Standard library imports.
22 # Standard library imports.
23 from types import FunctionType
23 from types import FunctionType
24
24
25 import __builtin__
25 import __builtin__
26 import codeop
26 import codeop
27 import compiler
27 import compiler
28 import sys
28 import sys
29 import traceback
29 import traceback
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.kernel.core import ultraTB
32 from IPython.kernel.core import ultraTB
33 from IPython.kernel.core.display_trap import DisplayTrap
33 from IPython.kernel.core.display_trap import DisplayTrap
34 from IPython.kernel.core.macro import Macro
34 from IPython.kernel.core.macro import Macro
35 from IPython.kernel.core.prompts import CachedOutput
35 from IPython.kernel.core.prompts import CachedOutput
36 from IPython.kernel.core.traceback_trap import TracebackTrap
36 from IPython.kernel.core.traceback_trap import TracebackTrap
37 from IPython.kernel.core.util import Bunch, system_shell
37 from IPython.kernel.core.util import Bunch, system_shell
38 from IPython.external.Itpl import ItplNS
38 from IPython.external.Itpl import ItplNS
39
39
40 # Global constants
40 # Global constants
41 COMPILER_ERROR = 'error'
41 COMPILER_ERROR = 'error'
42 INCOMPLETE_INPUT = 'incomplete'
42 INCOMPLETE_INPUT = 'incomplete'
43 COMPLETE_INPUT = 'complete'
43 COMPLETE_INPUT = 'complete'
44
44
45 ##############################################################################
45 ##############################################################################
46 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
46 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
47 # not
47 # not
48
48
49 rc = Bunch()
49 rc = Bunch()
50 rc.cache_size = 100
50 rc.cache_size = 100
51 rc.pprint = True
51 rc.pprint = True
52 rc.separate_in = '\n'
52 rc.separate_in = '\n'
53 rc.separate_out = '\n'
53 rc.separate_out = '\n'
54 rc.separate_out2 = ''
54 rc.separate_out2 = ''
55 rc.prompt_in1 = r'In [\#]: '
55 rc.prompt_in1 = r'In [\#]: '
56 rc.prompt_in2 = r' .\\D.: '
56 rc.prompt_in2 = r' .\\D.: '
57 rc.prompt_out = ''
57 rc.prompt_out = ''
58 rc.prompts_pad_left = False
58 rc.prompts_pad_left = False
59
59
60 ##############################################################################
60 ##############################################################################
61
61
62 # Top-level utilities
62 # Top-level utilities
63 def default_display_formatters():
63 def default_display_formatters():
64 """ Return a list of default display formatters.
64 """ Return a list of default display formatters.
65 """
65 """
66
66
67 from display_formatter import PPrintDisplayFormatter, ReprDisplayFormatter
67 from display_formatter import PPrintDisplayFormatter, ReprDisplayFormatter
68 return [PPrintDisplayFormatter(), ReprDisplayFormatter()]
68 return [PPrintDisplayFormatter(), ReprDisplayFormatter()]
69
69
70 def default_traceback_formatters():
70 def default_traceback_formatters():
71 """ Return a list of default traceback formatters.
71 """ Return a list of default traceback formatters.
72 """
72 """
73
73
74 from traceback_formatter import PlainTracebackFormatter
74 from traceback_formatter import PlainTracebackFormatter
75 return [PlainTracebackFormatter()]
75 return [PlainTracebackFormatter()]
76
76
77 # Top-level classes
77 # Top-level classes
78 class NotDefined(object): pass
78 class NotDefined(object): pass
79
79
80 class Interpreter(object):
80 class Interpreter(object):
81 """ An interpreter object.
81 """ An interpreter object.
82
82
83 fixme: needs to negotiate available formatters with frontends.
83 fixme: needs to negotiate available formatters with frontends.
84
84
85 Important: the interpeter should be built so that it exposes a method
85 Important: the interpeter should be built so that it exposes a method
86 for each attribute/method of its sub-object. This way it can be
86 for each attribute/method of its sub-object. This way it can be
87 replaced by a network adapter.
87 replaced by a network adapter.
88 """
88 """
89
89
90 def __init__(self, user_ns=None, global_ns=None,translator=None,
90 def __init__(self, user_ns=None, global_ns=None,translator=None,
91 magic=None, display_formatters=None,
91 magic=None, display_formatters=None,
92 traceback_formatters=None, output_trap=None, history=None,
92 traceback_formatters=None, output_trap=None, history=None,
93 message_cache=None, filename='<string>', config=None):
93 message_cache=None, filename='<string>', config=None):
94
94
95 # The local/global namespaces for code execution
95 # The local/global namespaces for code execution
96 local_ns = user_ns # compatibility name
96 local_ns = user_ns # compatibility name
97 if local_ns is None:
97 if local_ns is None:
98 local_ns = {}
98 local_ns = {}
99 self.user_ns = local_ns
99 self.user_ns = local_ns
100 # The local namespace
100 # The local namespace
101 if global_ns is None:
101 if global_ns is None:
102 global_ns = {}
102 global_ns = {}
103 self.user_global_ns = global_ns
103 self.user_global_ns = global_ns
104
104
105 # An object that will translate commands into executable Python.
105 # An object that will translate commands into executable Python.
106 # The current translator does not work properly so for now we are going
106 # The current translator does not work properly so for now we are going
107 # without!
107 # without!
108 # if translator is None:
108 # if translator is None:
109 # from IPython.kernel.core.translator import IPythonTranslator
109 # from IPython.kernel.core.translator import IPythonTranslator
110 # translator = IPythonTranslator()
110 # translator = IPythonTranslator()
111 self.translator = translator
111 self.translator = translator
112
112
113 # An object that maintains magic commands.
113 # An object that maintains magic commands.
114 if magic is None:
114 if magic is None:
115 from IPython.kernel.core.magic import Magic
115 from IPython.kernel.core.magic import Magic
116 magic = Magic(self)
116 magic = Magic(self)
117 self.magic = magic
117 self.magic = magic
118
118
119 # A list of formatters for the displayhook.
119 # A list of formatters for the displayhook.
120 if display_formatters is None:
120 if display_formatters is None:
121 display_formatters = default_display_formatters()
121 display_formatters = default_display_formatters()
122 self.display_formatters = display_formatters
122 self.display_formatters = display_formatters
123
123
124 # A list of formatters for tracebacks.
124 # A list of formatters for tracebacks.
125 if traceback_formatters is None:
125 if traceback_formatters is None:
126 traceback_formatters = default_traceback_formatters()
126 traceback_formatters = default_traceback_formatters()
127 self.traceback_formatters = traceback_formatters
127 self.traceback_formatters = traceback_formatters
128
128
129 # The object trapping stdout/stderr.
129 # The object trapping stdout/stderr.
130 if output_trap is None:
130 if output_trap is None:
131 from IPython.kernel.core.output_trap import OutputTrap
131 from IPython.kernel.core.output_trap import OutputTrap
132 output_trap = OutputTrap()
132 output_trap = OutputTrap()
133 self.output_trap = output_trap
133 self.output_trap = output_trap
134
134
135 # An object that manages the history.
135 # An object that manages the history.
136 if history is None:
136 if history is None:
137 from IPython.kernel.core.history import InterpreterHistory
137 from IPython.kernel.core.history import InterpreterHistory
138 history = InterpreterHistory()
138 history = InterpreterHistory()
139 self.history = history
139 self.history = history
140 self.get_history_item = history.get_history_item
140 self.get_history_item = history.get_history_item
141 self.get_history_input_cache = history.get_input_cache
141 self.get_history_input_cache = history.get_input_cache
142 self.get_history_input_after = history.get_input_after
142 self.get_history_input_after = history.get_input_after
143
143
144 # An object that caches all of the return messages.
144 # An object that caches all of the return messages.
145 if message_cache is None:
145 if message_cache is None:
146 from IPython.kernel.core.message_cache import SimpleMessageCache
146 from IPython.kernel.core.message_cache import SimpleMessageCache
147 message_cache = SimpleMessageCache()
147 message_cache = SimpleMessageCache()
148 self.message_cache = message_cache
148 self.message_cache = message_cache
149
149
150 # The "filename" of the code that is executed in this interpreter.
150 # The "filename" of the code that is executed in this interpreter.
151 self.filename = filename
151 self.filename = filename
152
152
153 # An object that contains much configuration information.
153 # An object that contains much configuration information.
154 if config is None:
154 if config is None:
155 # fixme: Move this constant elsewhere!
155 # fixme: Move this constant elsewhere!
156 config = Bunch(ESC_MAGIC='%')
156 config = Bunch(ESC_MAGIC='%')
157 self.config = config
157 self.config = config
158
158
159 # Hook managers.
159 # Hook managers.
160 # fixme: make the display callbacks configurable. In the meantime,
160 # fixme: make the display callbacks configurable. In the meantime,
161 # enable macros.
161 # enable macros.
162 self.display_trap = DisplayTrap(
162 self.display_trap = DisplayTrap(
163 formatters=self.display_formatters,
163 formatters=self.display_formatters,
164 callbacks=[self._possible_macro],
164 callbacks=[self._possible_macro],
165 )
165 )
166 self.traceback_trap = TracebackTrap(
166 self.traceback_trap = TracebackTrap(
167 formatters=self.traceback_formatters)
167 formatters=self.traceback_formatters)
168
168
169 # This is used temporarily for reformating exceptions in certain
169 # This is used temporarily for reformating exceptions in certain
170 # cases. It will go away once the ultraTB stuff is ported
170 # cases. It will go away once the ultraTB stuff is ported
171 # to ipython1
171 # to ipython1
172 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
172 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
173 mode='Context',
173 mode='Context',
174 tb_offset=2)
174 tb_offset=2)
175
175
176 # An object that can compile commands and remember __future__
176 # An object that can compile commands and remember __future__
177 # statements.
177 # statements.
178 self.command_compiler = codeop.CommandCompiler()
178 self.command_compiler = codeop.CommandCompiler()
179
179
180 # A replacement for the raw_input() and input() builtins. Change these
180 # A replacement for the raw_input() and input() builtins. Change these
181 # attributes later to configure them.
181 # attributes later to configure them.
182 self.raw_input_builtin = raw_input
182 self.raw_input_builtin = raw_input
183 self.input_builtin = input
183 self.input_builtin = input
184
184
185 # The number of the current cell.
185 # The number of the current cell.
186 self.current_cell_number = 1
186 self.current_cell_number = 1
187
187
188 # Initialize cache, set in/out prompts and printing system
188 # Initialize cache, set in/out prompts and printing system
189 self.outputcache = CachedOutput(self,
189 self.outputcache = CachedOutput(self,
190 rc.cache_size,
190 rc.cache_size,
191 rc.pprint,
191 rc.pprint,
192 input_sep = rc.separate_in,
192 input_sep = rc.separate_in,
193 output_sep = rc.separate_out,
193 output_sep = rc.separate_out,
194 output_sep2 = rc.separate_out2,
194 output_sep2 = rc.separate_out2,
195 ps1 = rc.prompt_in1,
195 ps1 = rc.prompt_in1,
196 ps2 = rc.prompt_in2,
196 ps2 = rc.prompt_in2,
197 ps_out = rc.prompt_out,
197 ps_out = rc.prompt_out,
198 pad_left = rc.prompts_pad_left)
198 pad_left = rc.prompts_pad_left)
199
199
200 # Need to decide later if this is the right approach, but clients
200 # Need to decide later if this is the right approach, but clients
201 # commonly use sys.ps1/2, so it may be best to just set them here
201 # commonly use sys.ps1/2, so it may be best to just set them here
202 sys.ps1 = self.outputcache.prompt1.p_str
202 sys.ps1 = self.outputcache.prompt1.p_str
203 sys.ps2 = self.outputcache.prompt2.p_str
203 sys.ps2 = self.outputcache.prompt2.p_str
204
204
205 # This is the message dictionary assigned temporarily when running the
205 # This is the message dictionary assigned temporarily when running the
206 # code.
206 # code.
207 self.message = None
207 self.message = None
208
208
209 self.setup_namespace()
209 self.setup_namespace()
210
210
211
211
212 #### Public 'Interpreter' interface ########################################
212 #### Public 'Interpreter' interface ########################################
213
213
214 def formatTraceback(self, et, ev, tb, message=''):
214 def formatTraceback(self, et, ev, tb, message=''):
215 """Put a formatted version of the traceback into value and reraise.
215 """Put a formatted version of the traceback into value and reraise.
216
216
217 When exceptions have to be sent over the network, the traceback
217 When exceptions have to be sent over the network, the traceback
218 needs to be put into the value of the exception in a nicely
218 needs to be put into the value of the exception in a nicely
219 formatted way. The method takes the type, value and tb of an
219 formatted way. The method takes the type, value and tb of an
220 exception and puts a string representation of the tb into the
220 exception and puts a string representation of the tb into the
221 value of the exception and reraises it.
221 value of the exception and reraises it.
222
222
223 Currently this method uses the ultraTb formatter from IPython trunk.
223 Currently this method uses the ultraTb formatter from IPython trunk.
224 Eventually it should simply use the traceback formatters in core
224 Eventually it should simply use the traceback formatters in core
225 that are loaded into self.tracback_trap.formatters.
225 that are loaded into self.tracback_trap.formatters.
226 """
226 """
227 tbinfo = self.tbHandler.text(et,ev,tb)
227 tbinfo = self.tbHandler.text(et,ev,tb)
228 ev._ipython_traceback_text = tbinfo
228 ev._ipython_traceback_text = tbinfo
229 return et, ev, tb
229 return et, ev, tb
230
230
231 def execute(self, commands, raiseException=True):
231 def execute(self, commands, raiseException=True):
232 """ Execute some IPython commands.
232 """ Execute some IPython commands.
233
233
234 1. Translate them into Python.
234 1. Translate them into Python.
235 2. Run them.
235 2. Run them.
236 3. Trap stdout/stderr.
236 3. Trap stdout/stderr.
237 4. Trap sys.displayhook().
237 4. Trap sys.displayhook().
238 5. Trap exceptions.
238 5. Trap exceptions.
239 6. Return a message object.
239 6. Return a message object.
240
240
241 Parameters
241 Parameters
242 ----------
242 ----------
243 commands : str
243 commands : str
244 The raw commands that the user typed into the prompt.
244 The raw commands that the user typed into the prompt.
245
245
246 Returns
246 Returns
247 -------
247 -------
248 message : dict
248 message : dict
249 The dictionary of responses. See the README.txt in this directory
249 The dictionary of responses. See the README.txt in this directory
250 for an explanation of the format.
250 for an explanation of the format.
251 """
251 """
252
252
253 # Create a message dictionary with all of the information we will be
253 # Create a message dictionary with all of the information we will be
254 # returning to the frontend and other listeners.
254 # returning to the frontend and other listeners.
255 message = self.setup_message()
255 message = self.setup_message()
256
256
257 # Massage the input and store the raw and translated commands into
257 # Massage the input and store the raw and translated commands into
258 # a dict.
258 # a dict.
259 user_input = dict(raw=commands)
259 user_input = dict(raw=commands)
260 if self.translator is not None:
260 if self.translator is not None:
261 python = self.translator(commands, message)
261 python = self.translator(commands, message)
262 if python is None:
262 if python is None:
263 # Something went wrong with the translation. The translator
263 # Something went wrong with the translation. The translator
264 # should have added an appropriate entry to the message object.
264 # should have added an appropriate entry to the message object.
265 return message
265 return message
266 else:
266 else:
267 python = commands
267 python = commands
268 user_input['translated'] = python
268 user_input['translated'] = python
269 message['input'] = user_input
269 message['input'] = user_input
270
270
271 # Set the message object so that any magics executed in the code have
271 # Set the message object so that any magics executed in the code have
272 # access.
272 # access.
273 self.message = message
273 self.message = message
274
274
275 # Set all of the output/exception traps.
275 # Set all of the output/exception traps.
276 self.set_traps()
276 self.set_traps()
277
277
278 # Actually execute the Python code.
278 # Actually execute the Python code.
279 status = self.execute_python(python)
279 status = self.execute_python(python)
280
280
281 # Unset all of the traps.
281 # Unset all of the traps.
282 self.unset_traps()
282 self.unset_traps()
283
283
284 # Unset the message object.
284 # Unset the message object.
285 self.message = None
285 self.message = None
286
286
287 # Update the history variables in the namespace.
287 # Update the history variables in the namespace.
288 # E.g. In, Out, _, __, ___
288 # E.g. In, Out, _, __, ___
289 if self.history is not None:
289 if self.history is not None:
290 self.history.update_history(self, python)
290 self.history.update_history(self, python)
291
291
292 # Let all of the traps contribute to the message and then clear their
292 # Let all of the traps contribute to the message and then clear their
293 # stored information.
293 # stored information.
294 self.output_trap.add_to_message(message)
294 self.output_trap.add_to_message(message)
295 self.output_trap.clear()
295 self.output_trap.clear()
296 self.display_trap.add_to_message(message)
296 self.display_trap.add_to_message(message)
297 self.display_trap.clear()
297 self.display_trap.clear()
298 self.traceback_trap.add_to_message(message)
298 self.traceback_trap.add_to_message(message)
299 # Pull out the type, value and tb of the current exception
299 # Pull out the type, value and tb of the current exception
300 # before clearing it.
300 # before clearing it.
301 einfo = self.traceback_trap.args
301 einfo = self.traceback_trap.args
302 self.traceback_trap.clear()
302 self.traceback_trap.clear()
303
303
304 # Cache the message.
304 # Cache the message.
305 self.message_cache.add_message(self.current_cell_number, message)
305 self.message_cache.add_message(self.current_cell_number, message)
306
306
307 # Bump the number.
307 # Bump the number.
308 self.current_cell_number += 1
308 self.current_cell_number += 1
309
309
310 # This conditional lets the execute method either raise any
310 # This conditional lets the execute method either raise any
311 # exception that has occured in user code OR return the message
311 # exception that has occured in user code OR return the message
312 # dict containing the traceback and other useful info.
312 # dict containing the traceback and other useful info.
313 if raiseException and einfo:
313 if raiseException and einfo:
314 raise einfo[0],einfo[1],einfo[2]
314 raise einfo[0],einfo[1],einfo[2]
315 else:
315 else:
316 return message
316 return message
317
317
318 def generate_prompt(self, is_continuation):
318 def generate_prompt(self, is_continuation):
319 """Calculate and return a string with the prompt to display.
319 """Calculate and return a string with the prompt to display.
320
320
321 :Parameters:
321 :Parameters:
322 is_continuation : bool
322 is_continuation : bool
323 Whether the input line is continuing multiline input or not, so
323 Whether the input line is continuing multiline input or not, so
324 that a proper continuation prompt can be computed."""
324 that a proper continuation prompt can be computed."""
325
325
326 if is_continuation:
326 if is_continuation:
327 return str(self.outputcache.prompt2)
327 return str(self.outputcache.prompt2)
328 else:
328 else:
329 return str(self.outputcache.prompt1)
329 return str(self.outputcache.prompt1)
330
330
331 def execute_python(self, python):
331 def execute_python(self, python):
332 """ Actually run the Python code in the namespace.
332 """ Actually run the Python code in the namespace.
333
333
334 :Parameters:
334 :Parameters:
335
335
336 python : str
336 python : str
337 Pure, exec'able Python code. Special IPython commands should have
337 Pure, exec'able Python code. Special IPython commands should have
338 already been translated into pure Python.
338 already been translated into pure Python.
339 """
339 """
340
340
341 # We use a CommandCompiler instance to compile the code so as to keep
341 # We use a CommandCompiler instance to compile the code so as to keep
342 # track of __future__ imports.
342 # track of __future__ imports.
343 try:
343 try:
344 commands = self.split_commands(python)
344 commands = self.split_commands(python)
345 except (SyntaxError, IndentationError), e:
345 except (SyntaxError, IndentationError), e:
346 # Save the exc_info so compilation related exceptions can be
346 # Save the exc_info so compilation related exceptions can be
347 # reraised
347 # reraised
348 self.traceback_trap.args = sys.exc_info()
348 self.traceback_trap.args = sys.exc_info()
349 self.pack_exception(self.message,e)
349 self.pack_exception(self.message,e)
350 return None
350 return None
351
351
352 for cmd in commands:
352 for cmd in commands:
353 try:
353 try:
354 code = self.command_compiler(cmd, self.filename, 'single')
354 code = self.command_compiler(cmd, self.filename, 'single')
355 except (SyntaxError, OverflowError, ValueError), e:
355 except (SyntaxError, OverflowError, ValueError), e:
356 self.traceback_trap.args = sys.exc_info()
356 self.traceback_trap.args = sys.exc_info()
357 self.pack_exception(self.message,e)
357 self.pack_exception(self.message,e)
358 # No point in continuing if one block raised
358 # No point in continuing if one block raised
359 return None
359 return None
360 else:
360 else:
361 self.execute_block(code)
361 self.execute_block(code)
362
362
363 def execute_block(self,code):
363 def execute_block(self,code):
364 """Execute a single block of code in the user namespace.
364 """Execute a single block of code in the user namespace.
365
365
366 Return value: a flag indicating whether the code to be run completed
366 Return value: a flag indicating whether the code to be run completed
367 successfully:
367 successfully:
368
368
369 - 0: successful execution.
369 - 0: successful execution.
370 - 1: an error occurred.
370 - 1: an error occurred.
371 """
371 """
372
372
373 outflag = 1 # start by assuming error, success will reset it
373 outflag = 1 # start by assuming error, success will reset it
374 try:
374 try:
375 exec code in self.user_ns
375 exec code in self.user_ns
376 outflag = 0
376 outflag = 0
377 except SystemExit:
377 except SystemExit:
378 self.resetbuffer()
378 self.resetbuffer()
379 self.traceback_trap.args = sys.exc_info()
379 self.traceback_trap.args = sys.exc_info()
380 except:
380 except:
381 self.traceback_trap.args = sys.exc_info()
381 self.traceback_trap.args = sys.exc_info()
382
382
383 return outflag
383 return outflag
384
384
385 def execute_macro(self, macro):
385 def execute_macro(self, macro):
386 """ Execute the value of a macro.
386 """ Execute the value of a macro.
387
387
388 Parameters
388 Parameters
389 ----------
389 ----------
390 macro : Macro
390 macro : Macro
391 """
391 """
392
392
393 python = macro.value
393 python = macro.value
394 if self.translator is not None:
394 if self.translator is not None:
395 python = self.translator(python)
395 python = self.translator(python)
396 self.execute_python(python)
396 self.execute_python(python)
397
397
398 def getCommand(self, i=None):
398 def getCommand(self, i=None):
399 """Gets the ith message in the message_cache.
399 """Gets the ith message in the message_cache.
400
400
401 This is implemented here for compatibility with the old ipython1 shell
401 This is implemented here for compatibility with the old ipython1 shell
402 I am not sure we need this though. I even seem to remember that we
402 I am not sure we need this though. I even seem to remember that we
403 were going to get rid of it.
403 were going to get rid of it.
404 """
404 """
405 return self.message_cache.get_message(i)
405 return self.message_cache.get_message(i)
406
406
407 def reset(self):
407 def reset(self):
408 """Reset the interpreter.
408 """Reset the interpreter.
409
409
410 Currently this only resets the users variables in the namespace.
410 Currently this only resets the users variables in the namespace.
411 In the future we might want to also reset the other stateful
411 In the future we might want to also reset the other stateful
412 things like that the Interpreter has, like In, Out, etc.
412 things like that the Interpreter has, like In, Out, etc.
413 """
413 """
414 self.user_ns.clear()
414 self.user_ns.clear()
415 self.setup_namespace()
415 self.setup_namespace()
416
416
417 def complete(self,line,text=None, pos=None):
417 def complete(self,line,text=None, pos=None):
418 """Complete the given text.
418 """Complete the given text.
419
419
420 :Parameters:
420 :Parameters:
421
421
422 text : str
422 text : str
423 Text fragment to be completed on. Typically this is
423 Text fragment to be completed on. Typically this is
424 """
424 """
425 # fixme: implement
425 # fixme: implement
426 raise NotImplementedError
426 raise NotImplementedError
427
427
428 def push(self, ns):
428 def push(self, ns):
429 """ Put value into the namespace with name key.
429 """ Put value into the namespace with name key.
430
430
431 Parameters
431 Parameters
432 ----------
432 ----------
433 **kwds
433 **kwds
434 """
434 """
435
435
436 self.user_ns.update(ns)
436 self.user_ns.update(ns)
437
437
438 def push_function(self, ns):
438 def push_function(self, ns):
439 # First set the func_globals for all functions to self.user_ns
439 # First set the func_globals for all functions to self.user_ns
440 new_kwds = {}
440 new_kwds = {}
441 for k, v in ns.iteritems():
441 for k, v in ns.iteritems():
442 if not isinstance(v, FunctionType):
442 if not isinstance(v, FunctionType):
443 raise TypeError("function object expected")
443 raise TypeError("function object expected")
444 new_kwds[k] = FunctionType(v.func_code, self.user_ns)
444 new_kwds[k] = FunctionType(v.func_code, self.user_ns)
445 self.user_ns.update(new_kwds)
445 self.user_ns.update(new_kwds)
446
446
447 def pack_exception(self,message,exc):
447 def pack_exception(self,message,exc):
448 message['exception'] = exc.__class__
448 message['exception'] = exc.__class__
449 message['exception_value'] = \
449 message['exception_value'] = \
450 traceback.format_exception_only(exc.__class__, exc)
450 traceback.format_exception_only(exc.__class__, exc)
451
451
452 def feed_block(self, source, filename='<input>', symbol='single'):
452 def feed_block(self, source, filename='<input>', symbol='single'):
453 """Compile some source in the interpreter.
453 """Compile some source in the interpreter.
454
454
455 One several things can happen:
455 One several things can happen:
456
456
457 1) The input is incorrect; compile_command() raised an
457 1) The input is incorrect; compile_command() raised an
458 exception (SyntaxError or OverflowError).
458 exception (SyntaxError or OverflowError).
459
459
460 2) The input is incomplete, and more input is required;
460 2) The input is incomplete, and more input is required;
461 compile_command() returned None. Nothing happens.
461 compile_command() returned None. Nothing happens.
462
462
463 3) The input is complete; compile_command() returned a code
463 3) The input is complete; compile_command() returned a code
464 object. The code is executed by calling self.runcode() (which
464 object. The code is executed by calling self.runcode() (which
465 also handles run-time exceptions, except for SystemExit).
465 also handles run-time exceptions, except for SystemExit).
466
466
467 The return value is:
467 The return value is:
468
468
469 - True in case 2
469 - True in case 2
470
470
471 - False in the other cases, unless an exception is raised, where
471 - False in the other cases, unless an exception is raised, where
472 None is returned instead. This can be used by external callers to
472 None is returned instead. This can be used by external callers to
473 know whether to continue feeding input or not.
473 know whether to continue feeding input or not.
474
474
475 The return value can be used to decide whether to use sys.ps1 or
475 The return value can be used to decide whether to use sys.ps1 or
476 sys.ps2 to prompt the next line."""
476 sys.ps2 to prompt the next line."""
477
477
478 self.message = self.setup_message()
478 self.message = self.setup_message()
479
479
480 try:
480 try:
481 code = self.command_compiler(source,filename,symbol)
481 code = self.command_compiler(source,filename,symbol)
482 except (OverflowError, SyntaxError, IndentationError, ValueError ), e:
482 except (OverflowError, SyntaxError, IndentationError, ValueError ), e:
483 # Case 1
483 # Case 1
484 self.traceback_trap.args = sys.exc_info()
484 self.traceback_trap.args = sys.exc_info()
485 self.pack_exception(self.message,e)
485 self.pack_exception(self.message,e)
486 return COMPILER_ERROR,False
486 return COMPILER_ERROR,False
487
487
488 if code is None:
488 if code is None:
489 # Case 2: incomplete input. This means that the input can span
489 # Case 2: incomplete input. This means that the input can span
490 # multiple lines. But we still need to decide when to actually
490 # multiple lines. But we still need to decide when to actually
491 # stop taking user input. Later we'll add auto-indentation support
491 # stop taking user input. Later we'll add auto-indentation support
492 # somehow. In the meantime, we'll just stop if there are two lines
492 # somehow. In the meantime, we'll just stop if there are two lines
493 # of pure whitespace at the end.
493 # of pure whitespace at the end.
494 last_two = source.rsplit('\n',2)[-2:]
494 last_two = source.rsplit('\n',2)[-2:]
495 print 'last two:',last_two # dbg
495 print 'last two:',last_two # dbg
496 if len(last_two)==2 and all(s.isspace() for s in last_two):
496 if len(last_two)==2 and all(s.isspace() for s in last_two):
497 return COMPLETE_INPUT,False
497 return COMPLETE_INPUT,False
498 else:
498 else:
499 return INCOMPLETE_INPUT, True
499 return INCOMPLETE_INPUT, True
500 else:
500 else:
501 # Case 3
501 # Case 3
502 return COMPLETE_INPUT, False
502 return COMPLETE_INPUT, False
503
503
504 def pull(self, keys):
504 def pull(self, keys):
505 """ Get an item out of the namespace by key.
505 """ Get an item out of the namespace by key.
506
506
507 Parameters
507 Parameters
508 ----------
508 ----------
509 key : str
509 key : str
510
510
511 Returns
511 Returns
512 -------
512 -------
513 value : object
513 value : object
514
514
515 Raises
515 Raises
516 ------
516 ------
517 TypeError if the key is not a string.
517 TypeError if the key is not a string.
518 NameError if the object doesn't exist.
518 NameError if the object doesn't exist.
519 """
519 """
520
520
521 if isinstance(keys, str):
521 if isinstance(keys, str):
522 result = self.user_ns.get(keys, NotDefined())
522 result = self.user_ns.get(keys, NotDefined())
523 if isinstance(result, NotDefined):
523 if isinstance(result, NotDefined):
524 raise NameError('name %s is not defined' % keys)
524 raise NameError('name %s is not defined' % keys)
525 elif isinstance(keys, (list, tuple)):
525 elif isinstance(keys, (list, tuple)):
526 result = []
526 result = []
527 for key in keys:
527 for key in keys:
528 if not isinstance(key, str):
528 if not isinstance(key, str):
529 raise TypeError("objects must be keyed by strings.")
529 raise TypeError("objects must be keyed by strings.")
530 else:
530 else:
531 r = self.user_ns.get(key, NotDefined())
531 r = self.user_ns.get(key, NotDefined())
532 if isinstance(r, NotDefined):
532 if isinstance(r, NotDefined):
533 raise NameError('name %s is not defined' % key)
533 raise NameError('name %s is not defined' % key)
534 else:
534 else:
535 result.append(r)
535 result.append(r)
536 if len(keys)==1:
536 if len(keys)==1:
537 result = result[0]
537 result = result[0]
538 else:
538 else:
539 raise TypeError("keys must be a strong or a list/tuple of strings")
539 raise TypeError("keys must be a strong or a list/tuple of strings")
540 return result
540 return result
541
541
542 def pull_function(self, keys):
542 def pull_function(self, keys):
543 return self.pull(keys)
543 return self.pull(keys)
544
544
545 #### Interactive user API ##################################################
545 #### Interactive user API ##################################################
546
546
547 def ipsystem(self, command):
547 def ipsystem(self, command):
548 """ Execute a command in a system shell while expanding variables in the
548 """ Execute a command in a system shell while expanding variables in the
549 current namespace.
549 current namespace.
550
550
551 Parameters
551 Parameters
552 ----------
552 ----------
553 command : str
553 command : str
554 """
554 """
555
555
556 # Expand $variables.
556 # Expand $variables.
557 command = self.var_expand(command)
557 command = self.var_expand(command)
558
558
559 system_shell(command,
559 system_shell(command,
560 header='IPython system call: ',
560 header='IPython system call: ',
561 verbose=self.rc.system_verbose,
561 verbose=self.rc.system_verbose,
562 )
562 )
563
563
564 def ipmagic(self, arg_string):
564 def ipmagic(self, arg_string):
565 """ Call a magic function by name.
565 """ Call a magic function by name.
566
566
567 ipmagic('name -opt foo bar') is equivalent to typing at the ipython
567 ipmagic('name -opt foo bar') is equivalent to typing at the ipython
568 prompt:
568 prompt:
569
569
570 In[1]: %name -opt foo bar
570 In[1]: %name -opt foo bar
571
571
572 To call a magic without arguments, simply use ipmagic('name').
572 To call a magic without arguments, simply use ipmagic('name').
573
573
574 This provides a proper Python function to call IPython's magics in any
574 This provides a proper Python function to call IPython's magics in any
575 valid Python code you can type at the interpreter, including loops and
575 valid Python code you can type at the interpreter, including loops and
576 compound statements. It is added by IPython to the Python builtin
576 compound statements. It is added by IPython to the Python builtin
577 namespace upon initialization.
577 namespace upon initialization.
578
578
579 Parameters
579 Parameters
580 ----------
580 ----------
581 arg_string : str
581 arg_string : str
582 A string containing the name of the magic function to call and any
582 A string containing the name of the magic function to call and any
583 additional arguments to be passed to the magic.
583 additional arguments to be passed to the magic.
584
584
585 Returns
585 Returns
586 -------
586 -------
587 something : object
587 something : object
588 The return value of the actual object.
588 The return value of the actual object.
589 """
589 """
590
590
591 # Taken from IPython.
591 # Taken from IPython.
592 raise NotImplementedError('Not ported yet')
592 raise NotImplementedError('Not ported yet')
593
593
594 args = arg_string.split(' ', 1)
594 args = arg_string.split(' ', 1)
595 magic_name = args[0]
595 magic_name = args[0]
596 magic_name = magic_name.lstrip(self.config.ESC_MAGIC)
596 magic_name = magic_name.lstrip(self.config.ESC_MAGIC)
597
597
598 try:
598 try:
599 magic_args = args[1]
599 magic_args = args[1]
600 except IndexError:
600 except IndexError:
601 magic_args = ''
601 magic_args = ''
602 fn = getattr(self.magic, 'magic_'+magic_name, None)
602 fn = getattr(self.magic, 'magic_'+magic_name, None)
603 if fn is None:
603 if fn is None:
604 self.error("Magic function `%s` not found." % magic_name)
604 self.error("Magic function `%s` not found." % magic_name)
605 else:
605 else:
606 magic_args = self.var_expand(magic_args)
606 magic_args = self.var_expand(magic_args)
607 return fn(magic_args)
607 return fn(magic_args)
608
608
609
609
610 #### Private 'Interpreter' interface #######################################
610 #### Private 'Interpreter' interface #######################################
611
611
612 def setup_message(self):
612 def setup_message(self):
613 """Return a message object.
613 """Return a message object.
614
614
615 This method prepares and returns a message dictionary. This dict
615 This method prepares and returns a message dictionary. This dict
616 contains the various fields that are used to transfer information about
616 contains the various fields that are used to transfer information about
617 execution, results, tracebacks, etc, to clients (either in or out of
617 execution, results, tracebacks, etc, to clients (either in or out of
618 process ones). Because of the need to work with possibly out of
618 process ones). Because of the need to work with possibly out of
619 process clients, this dict MUST contain strictly pickle-safe values.
619 process clients, this dict MUST contain strictly pickle-safe values.
620 """
620 """
621
621
622 return dict(number=self.current_cell_number)
622 return dict(number=self.current_cell_number)
623
623
624 def setup_namespace(self):
624 def setup_namespace(self):
625 """ Add things to the namespace.
625 """ Add things to the namespace.
626 """
626 """
627
627
628 self.user_ns.setdefault('__name__', '__main__')
628 self.user_ns.setdefault('__name__', '__main__')
629 self.user_ns.setdefault('__builtins__', __builtin__)
629 self.user_ns.setdefault('__builtins__', __builtin__)
630 self.user_ns['__IP'] = self
630 self.user_ns['__IP'] = self
631 if self.raw_input_builtin is not None:
631 if self.raw_input_builtin is not None:
632 self.user_ns['raw_input'] = self.raw_input_builtin
632 self.user_ns['raw_input'] = self.raw_input_builtin
633 if self.input_builtin is not None:
633 if self.input_builtin is not None:
634 self.user_ns['input'] = self.input_builtin
634 self.user_ns['input'] = self.input_builtin
635
635
636 builtin_additions = dict(
636 builtin_additions = dict(
637 ipmagic=self.ipmagic,
637 ipmagic=self.ipmagic,
638 )
638 )
639 __builtin__.__dict__.update(builtin_additions)
639 __builtin__.__dict__.update(builtin_additions)
640
640
641 if self.history is not None:
641 if self.history is not None:
642 self.history.setup_namespace(self.user_ns)
642 self.history.setup_namespace(self.user_ns)
643
643
644 def set_traps(self):
644 def set_traps(self):
645 """ Set all of the output, display, and traceback traps.
645 """ Set all of the output, display, and traceback traps.
646 """
646 """
647
647
648 self.output_trap.set()
648 self.output_trap.set()
649 self.display_trap.set()
649 self.display_trap.set()
650 self.traceback_trap.set()
650 self.traceback_trap.set()
651
651
652 def unset_traps(self):
652 def unset_traps(self):
653 """ Unset all of the output, display, and traceback traps.
653 """ Unset all of the output, display, and traceback traps.
654 """
654 """
655
655
656 self.output_trap.unset()
656 self.output_trap.unset()
657 self.display_trap.unset()
657 self.display_trap.unset()
658 self.traceback_trap.unset()
658 self.traceback_trap.unset()
659
659
660 def split_commands(self, python):
660 def split_commands(self, python):
661 """ Split multiple lines of code into discrete commands that can be
661 """ Split multiple lines of code into discrete commands that can be
662 executed singly.
662 executed singly.
663
663
664 Parameters
664 Parameters
665 ----------
665 ----------
666 python : str
666 python : str
667 Pure, exec'able Python code.
667 Pure, exec'able Python code.
668
668
669 Returns
669 Returns
670 -------
670 -------
671 commands : list of str
671 commands : list of str
672 Separate commands that can be exec'ed independently.
672 Separate commands that can be exec'ed independently.
673 """
673 """
674
674
675 # compiler.parse treats trailing spaces after a newline as a
675 # compiler.parse treats trailing spaces after a newline as a
676 # SyntaxError. This is different than codeop.CommandCompiler, which
676 # SyntaxError. This is different than codeop.CommandCompiler, which
677 # will compile the trailng spaces just fine. We simply strip any
677 # will compile the trailng spaces just fine. We simply strip any
678 # trailing whitespace off. Passing a string with trailing whitespace
678 # trailing whitespace off. Passing a string with trailing whitespace
679 # to exec will fail however. There seems to be some inconsistency in
679 # to exec will fail however. There seems to be some inconsistency in
680 # how trailing whitespace is handled, but this seems to work.
680 # how trailing whitespace is handled, but this seems to work.
681 python = python.strip()
681 python = python.strip()
682
682
683 # The compiler module does not like unicode. We need to convert
684 # it encode it:
685 if isinstance(python, unicode):
686 # Use the utf-8-sig BOM so the compiler detects this a UTF-8
687 # encode string.
688 python = '\xef\xbb\xbf' + python.encode('utf-8')
689
683 # The compiler module will parse the code into an abstract syntax tree.
690 # The compiler module will parse the code into an abstract syntax tree.
684 ast = compiler.parse(python)
691 ast = compiler.parse(python)
685
692
686 # Uncomment to help debug the ast tree
693 # Uncomment to help debug the ast tree
687 # for n in ast.node:
694 # for n in ast.node:
688 # print n.lineno,'->',n
695 # print n.lineno,'->',n
689
696
690 # Each separate command is available by iterating over ast.node. The
697 # Each separate command is available by iterating over ast.node. The
691 # lineno attribute is the line number (1-indexed) beginning the commands
698 # lineno attribute is the line number (1-indexed) beginning the commands
692 # suite.
699 # suite.
693 # lines ending with ";" yield a Discard Node that doesn't have a lineno
700 # lines ending with ";" yield a Discard Node that doesn't have a lineno
694 # attribute. These nodes can and should be discarded. But there are
701 # attribute. These nodes can and should be discarded. But there are
695 # other situations that cause Discard nodes that shouldn't be discarded.
702 # other situations that cause Discard nodes that shouldn't be discarded.
696 # We might eventually discover other cases where lineno is None and have
703 # We might eventually discover other cases where lineno is None and have
697 # to put in a more sophisticated test.
704 # to put in a more sophisticated test.
698 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
705 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
699
706
700 # When we finally get the slices, we will need to slice all the way to
707 # When we finally get the slices, we will need to slice all the way to
701 # the end even though we don't have a line number for it. Fortunately,
708 # the end even though we don't have a line number for it. Fortunately,
702 # None does the job nicely.
709 # None does the job nicely.
703 linenos.append(None)
710 linenos.append(None)
704 lines = python.splitlines()
711 lines = python.splitlines()
705
712
706 # Create a list of atomic commands.
713 # Create a list of atomic commands.
707 cmds = []
714 cmds = []
708 for i, j in zip(linenos[:-1], linenos[1:]):
715 for i, j in zip(linenos[:-1], linenos[1:]):
709 cmd = lines[i:j]
716 cmd = lines[i:j]
710 if cmd:
717 if cmd:
711 cmds.append('\n'.join(cmd)+'\n')
718 cmds.append('\n'.join(cmd)+'\n')
712
719
713 return cmds
720 return cmds
714
721
715 def error(self, text):
722 def error(self, text):
716 """ Pass an error message back to the shell.
723 """ Pass an error message back to the shell.
717
724
718 Preconditions
725 Preconditions
719 -------------
726 -------------
720 This should only be called when self.message is set. In other words,
727 This should only be called when self.message is set. In other words,
721 when code is being executed.
728 when code is being executed.
722
729
723 Parameters
730 Parameters
724 ----------
731 ----------
725 text : str
732 text : str
726 """
733 """
727
734
728 errors = self.message.get('IPYTHON_ERROR', [])
735 errors = self.message.get('IPYTHON_ERROR', [])
729 errors.append(text)
736 errors.append(text)
730
737
731 def var_expand(self, template):
738 def var_expand(self, template):
732 """ Expand $variables in the current namespace using Itpl.
739 """ Expand $variables in the current namespace using Itpl.
733
740
734 Parameters
741 Parameters
735 ----------
742 ----------
736 template : str
743 template : str
737 """
744 """
738
745
739 return str(ItplNS(template, self.user_ns))
746 return str(ItplNS(template, self.user_ns))
740
747
741 def _possible_macro(self, obj):
748 def _possible_macro(self, obj):
742 """ If the object is a macro, execute it.
749 """ If the object is a macro, execute it.
743 """
750 """
744
751
745 if isinstance(obj, Macro):
752 if isinstance(obj, Macro):
746 self.execute_macro(obj)
753 self.execute_macro(obj)
747
754
@@ -1,324 +1,347 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 conveniently, either locally or remotely.
4 """Start an IPython cluster conveniently, either locally or remotely.
5
5
6 Basic usage
6 Basic usage
7 -----------
7 -----------
8
8
9 For local operation, the simplest mode of usage is:
9 For local operation, the simplest mode of usage is:
10
10
11 %prog -n N
11 %prog -n N
12
12
13 where N is the number of engines you want started.
13 where N is the number of engines you want started.
14
14
15 For remote operation, you must call it with a cluster description file:
15 For remote operation, you must call it with a cluster description file:
16
16
17 %prog -f clusterfile.py
17 %prog -f clusterfile.py
18
18
19 The cluster file is a normal Python script which gets run via execfile(). You
19 The cluster file is a normal Python script which gets run via execfile(). You
20 can have arbitrary logic in it, but all that matters is that at the end of the
20 can have arbitrary logic in it, but all that matters is that at the end of the
21 execution, it declares the variables 'controller', 'engines', and optionally
21 execution, it declares the variables 'controller', 'engines', and optionally
22 'sshx'. See the accompanying examples for details on what these variables must
22 'sshx'. See the accompanying examples for details on what these variables must
23 contain.
23 contain.
24
24
25
25
26 Notes
26 Notes
27 -----
27 -----
28
28
29 WARNING: this code is still UNFINISHED and EXPERIMENTAL! It is incomplete,
29 WARNING: this code is still UNFINISHED and EXPERIMENTAL! It is incomplete,
30 some listed options are not really implemented, and all of its interfaces are
30 some listed options are not really implemented, and all of its interfaces are
31 subject to change.
31 subject to change.
32
32
33 When operating over SSH for a remote cluster, this program relies on the
33 When operating over SSH for a remote cluster, this program relies on the
34 existence of a particular script called 'sshx'. This script must live in the
34 existence of a particular script called 'sshx'. This script must live in the
35 target systems where you'll be running your controller and engines, and is
35 target systems where you'll be running your controller and engines, and is
36 needed to configure your PATH and PYTHONPATH variables for further execution of
36 needed to configure your PATH and PYTHONPATH variables for further execution of
37 python code at the other end of an SSH connection. The script can be as simple
37 python code at the other end of an SSH connection. The script can be as simple
38 as:
38 as:
39
39
40 #!/bin/sh
40 #!/bin/sh
41 . $HOME/.bashrc
41 . $HOME/.bashrc
42 "$@"
42 "$@"
43
43
44 which is the default one provided by IPython. You can modify this or provide
44 which is the default one provided by IPython. You can modify this or provide
45 your own. Since it's quite likely that for different clusters you may need
45 your own. Since it's quite likely that for different clusters you may need
46 this script to configure things differently or that it may live in different
46 this script to configure things differently or that it may live in different
47 locations, its full path can be set in the same file where you define the
47 locations, its full path can be set in the same file where you define the
48 cluster setup. IPython's order of evaluation for this variable is the
48 cluster setup. IPython's order of evaluation for this variable is the
49 following:
49 following:
50
50
51 a) Internal default: 'sshx'. This only works if it is in the default system
51 a) Internal default: 'sshx'. This only works if it is in the default system
52 path which SSH sets up in non-interactive mode.
52 path which SSH sets up in non-interactive mode.
53
53
54 b) Environment variable: if $IPYTHON_SSHX is defined, this overrides the
54 b) Environment variable: if $IPYTHON_SSHX is defined, this overrides the
55 internal default.
55 internal default.
56
56
57 c) Variable 'sshx' in the cluster configuration file: finally, this will
57 c) Variable 'sshx' in the cluster configuration file: finally, this will
58 override the previous two values.
58 override the previous two values.
59
59
60 This code is Unix-only, with precious little hope of any of this ever working
60 This code is Unix-only, with precious little hope of any of this ever working
61 under Windows, since we need SSH from the ground up, we background processes,
61 under Windows, since we need SSH from the ground up, we background processes,
62 etc. Ports of this functionality to Windows are welcome.
62 etc. Ports of this functionality to Windows are welcome.
63
63
64
64
65 Call summary
65 Call summary
66 ------------
66 ------------
67
67
68 %prog [options]
68 %prog [options]
69 """
69 """
70
70
71 __docformat__ = "restructuredtext en"
71 __docformat__ = "restructuredtext en"
72
72
73 #-------------------------------------------------------------------------------
73 #-------------------------------------------------------------------------------
74 # Copyright (C) 2008 The IPython Development Team
74 # Copyright (C) 2008 The IPython Development Team
75 #
75 #
76 # Distributed under the terms of the BSD License. The full license is in
76 # Distributed under the terms of the BSD License. The full license is in
77 # the file COPYING, distributed as part of this software.
77 # the file COPYING, distributed as part of this software.
78 #-------------------------------------------------------------------------------
78 #-------------------------------------------------------------------------------
79
79
80 #-------------------------------------------------------------------------------
80 #-------------------------------------------------------------------------------
81 # Stdlib imports
81 # Stdlib imports
82 #-------------------------------------------------------------------------------
82 #-------------------------------------------------------------------------------
83
83
84 import os
84 import os
85 import signal
85 import signal
86 import sys
86 import sys
87 import time
87 import time
88
88
89 from optparse import OptionParser
89 from optparse import OptionParser
90 from subprocess import Popen,call
90 from subprocess import Popen,call
91
91
92 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
93 # IPython imports
93 # IPython imports
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 from IPython.tools import utils
95 from IPython.tools import utils
96 from IPython.config import cutils
96 from IPython.genutils import get_ipython_dir
97
97
98 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
99 # Normal code begins
99 # Normal code begins
100 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
101
101
102 def parse_args():
102 def parse_args():
103 """Parse command line and return opts,args."""
103 """Parse command line and return opts,args."""
104
104
105 parser = OptionParser(usage=__doc__)
105 parser = OptionParser(usage=__doc__)
106 newopt = parser.add_option # shorthand
106 newopt = parser.add_option # shorthand
107
107
108 newopt("--controller-port", type="int", dest="controllerport",
108 newopt("--controller-port", type="int", dest="controllerport",
109 help="the TCP port the controller is listening on")
109 help="the TCP port the controller is listening on")
110
110
111 newopt("--controller-ip", type="string", dest="controllerip",
111 newopt("--controller-ip", type="string", dest="controllerip",
112 help="the TCP ip address of the controller")
112 help="the TCP ip address of the controller")
113
113
114 newopt("-n", "--num", type="int", dest="n",default=2,
114 newopt("-n", "--num", type="int", dest="n",default=2,
115 help="the number of engines to start")
115 help="the number of engines to start")
116
116
117 newopt("--engine-port", type="int", dest="engineport",
117 newopt("--engine-port", type="int", dest="engineport",
118 help="the TCP port the controller will listen on for engine "
118 help="the TCP port the controller will listen on for engine "
119 "connections")
119 "connections")
120
120
121 newopt("--engine-ip", type="string", dest="engineip",
121 newopt("--engine-ip", type="string", dest="engineip",
122 help="the TCP ip address the controller will listen on "
122 help="the TCP ip address the controller will listen on "
123 "for engine connections")
123 "for engine connections")
124
124
125 newopt("--mpi", type="string", dest="mpi",
125 newopt("--mpi", type="string", dest="mpi",
126 help="use mpi with package: for instance --mpi=mpi4py")
126 help="use mpi with package: for instance --mpi=mpi4py")
127
127
128 newopt("-l", "--logfile", type="string", dest="logfile",
128 newopt("-l", "--logfile", type="string", dest="logfile",
129 help="log file name")
129 help="log file name")
130
130
131 newopt('-f','--cluster-file',dest='clusterfile',
131 newopt('-f','--cluster-file',dest='clusterfile',
132 help='file describing a remote cluster')
132 help='file describing a remote cluster')
133
133
134 return parser.parse_args()
134 return parser.parse_args()
135
135
136 def numAlive(controller,engines):
136 def numAlive(controller,engines):
137 """Return the number of processes still alive."""
137 """Return the number of processes still alive."""
138 retcodes = [controller.poll()] + \
138 retcodes = [controller.poll()] + \
139 [e.poll() for e in engines]
139 [e.poll() for e in engines]
140 return retcodes.count(None)
140 return retcodes.count(None)
141
141
142 stop = lambda pid: os.kill(pid,signal.SIGINT)
142 stop = lambda pid: os.kill(pid,signal.SIGINT)
143 kill = lambda pid: os.kill(pid,signal.SIGTERM)
143 kill = lambda pid: os.kill(pid,signal.SIGTERM)
144
144
145 def cleanup(clean,controller,engines):
145 def cleanup(clean,controller,engines):
146 """Stop the controller and engines with the given cleanup method."""
146 """Stop the controller and engines with the given cleanup method."""
147
147
148 for e in engines:
148 for e in engines:
149 if e.poll() is None:
149 if e.poll() is None:
150 print 'Stopping engine, pid',e.pid
150 print 'Stopping engine, pid',e.pid
151 clean(e.pid)
151 clean(e.pid)
152 if controller.poll() is None:
152 if controller.poll() is None:
153 print 'Stopping controller, pid',controller.pid
153 print 'Stopping controller, pid',controller.pid
154 clean(controller.pid)
154 clean(controller.pid)
155
155
156
156
157 def ensureDir(path):
157 def ensureDir(path):
158 """Ensure a directory exists or raise an exception."""
158 """Ensure a directory exists or raise an exception."""
159 if not os.path.isdir(path):
159 if not os.path.isdir(path):
160 os.makedirs(path)
160 os.makedirs(path)
161
161
162
162
163 def startMsg(control_host,control_port=10105):
163 def startMsg(control_host,control_port=10105):
164 """Print a startup message"""
164 """Print a startup message"""
165 print
165 print
166 print 'Your cluster is up and running.'
166 print 'Your cluster is up and running.'
167 print
167 print
168 print 'For interactive use, you can make a MultiEngineClient with:'
168 print 'For interactive use, you can make a MultiEngineClient with:'
169 print
169 print
170 print 'from IPython.kernel import client'
170 print 'from IPython.kernel import client'
171 print "mec = client.MultiEngineClient()"
171 print "mec = client.MultiEngineClient()"
172 print
172 print
173 print 'You can then cleanly stop the cluster from IPython using:'
173 print 'You can then cleanly stop the cluster from IPython using:'
174 print
174 print
175 print 'mec.kill(controller=True)'
175 print 'mec.kill(controller=True)'
176 print
176 print
177
177
178
178
179 def clusterLocal(opt,arg):
179 def clusterLocal(opt,arg):
180 """Start a cluster on the local machine."""
180 """Start a cluster on the local machine."""
181
181
182 # Store all logs inside the ipython directory
182 # Store all logs inside the ipython directory
183 ipdir = cutils.get_ipython_dir()
183 ipdir = get_ipython_dir()
184 pjoin = os.path.join
184 pjoin = os.path.join
185
185
186 logfile = opt.logfile
186 logfile = opt.logfile
187 if logfile is None:
187 if logfile is None:
188 logdir_base = pjoin(ipdir,'log')
188 logdir_base = pjoin(ipdir,'log')
189 ensureDir(logdir_base)
189 ensureDir(logdir_base)
190 logfile = pjoin(logdir_base,'ipcluster-')
190 logfile = pjoin(logdir_base,'ipcluster-')
191
191
192 print 'Starting controller:',
192 print 'Starting controller:',
193 controller = Popen(['ipcontroller','--logfile',logfile,'-x','-y'])
193 controller = Popen(['ipcontroller','--logfile',logfile,'-x','-y'])
194 print 'Controller PID:',controller.pid
194 print 'Controller PID:',controller.pid
195
195
196 print 'Starting engines: ',
196 print 'Starting engines: ',
197 time.sleep(5)
197 time.sleep(5)
198
198
199 englogfile = '%s%s-' % (logfile,controller.pid)
199 englogfile = '%s%s-' % (logfile,controller.pid)
200 mpi = opt.mpi
200 mpi = opt.mpi
201 if mpi: # start with mpi - killing the engines with sigterm will not work if you do this
201 if mpi: # start with mpi - killing the engines with sigterm will not work if you do this
202 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi',
202 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi',
203 mpi, '--logfile',englogfile])]
203 mpi, '--logfile',englogfile])]
204 # engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi])]
204 # engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi])]
205 else: # do what we would normally do
205 else: # do what we would normally do
206 engines = [ Popen(['ipengine','--logfile',englogfile])
206 engines = [ Popen(['ipengine','--logfile',englogfile])
207 for i in range(opt.n) ]
207 for i in range(opt.n) ]
208 eids = [e.pid for e in engines]
208 eids = [e.pid for e in engines]
209 print 'Engines PIDs: ',eids
209 print 'Engines PIDs: ',eids
210 print 'Log files: %s*' % englogfile
210 print 'Log files: %s*' % englogfile
211
211
212 proc_ids = eids + [controller.pid]
212 proc_ids = eids + [controller.pid]
213 procs = engines + [controller]
213 procs = engines + [controller]
214
214
215 grpid = os.getpgrp()
215 grpid = os.getpgrp()
216 try:
216 try:
217 startMsg('127.0.0.1')
217 startMsg('127.0.0.1')
218 print 'You can also hit Ctrl-C to stop it, or use from the cmd line:'
218 print 'You can also hit Ctrl-C to stop it, or use from the cmd line:'
219 print
219 print
220 print 'kill -INT',grpid
220 print 'kill -INT',grpid
221 print
221 print
222 try:
222 try:
223 while True:
223 while True:
224 time.sleep(5)
224 time.sleep(5)
225 except:
225 except:
226 pass
226 pass
227 finally:
227 finally:
228 print 'Stopping cluster. Cleaning up...'
228 print 'Stopping cluster. Cleaning up...'
229 cleanup(stop,controller,engines)
229 cleanup(stop,controller,engines)
230 for i in range(4):
230 for i in range(4):
231 time.sleep(i+2)
231 time.sleep(i+2)
232 nZombies = numAlive(controller,engines)
232 nZombies = numAlive(controller,engines)
233 if nZombies== 0:
233 if nZombies== 0:
234 print 'OK: All processes cleaned up.'
234 print 'OK: All processes cleaned up.'
235 break
235 break
236 print 'Trying again, %d processes did not stop...' % nZombies
236 print 'Trying again, %d processes did not stop...' % nZombies
237 cleanup(kill,controller,engines)
237 cleanup(kill,controller,engines)
238 if numAlive(controller,engines) == 0:
238 if numAlive(controller,engines) == 0:
239 print 'OK: All processes cleaned up.'
239 print 'OK: All processes cleaned up.'
240 break
240 break
241 else:
241 else:
242 print '*'*75
242 print '*'*75
243 print 'ERROR: could not kill some processes, try to do it',
243 print 'ERROR: could not kill some processes, try to do it',
244 print 'manually.'
244 print 'manually.'
245 zombies = []
245 zombies = []
246 if controller.returncode is None:
246 if controller.returncode is None:
247 print 'Controller is alive: pid =',controller.pid
247 print 'Controller is alive: pid =',controller.pid
248 zombies.append(controller.pid)
248 zombies.append(controller.pid)
249 liveEngines = [ e for e in engines if e.returncode is None ]
249 liveEngines = [ e for e in engines if e.returncode is None ]
250 for e in liveEngines:
250 for e in liveEngines:
251 print 'Engine is alive: pid =',e.pid
251 print 'Engine is alive: pid =',e.pid
252 zombies.append(e.pid)
252 zombies.append(e.pid)
253 print
253 print
254 print 'Zombie summary:',' '.join(map(str,zombies))
254 print 'Zombie summary:',' '.join(map(str,zombies))
255
255
256 def clusterRemote(opt,arg):
256 def clusterRemote(opt,arg):
257 """Start a remote cluster over SSH"""
257 """Start a remote cluster over SSH"""
258
258
259 # B. Granger, 9/3/08
260 # The launching of a remote cluster using SSH and a clusterfile
261 # is broken. Because it won't be fixed before the 0.9 release,
262 # we are removing it. For now, we just print a message to the
263 # user and abort.
264
265 print """The launching of a remote IPython cluster using SSL
266 and a clusterfile has been removed in this release.
267 It has been broken for a while and we are in the process
268 of building a new process management system that will be
269 used to provide a more robust way of starting an IPython
270 cluster.
271
272 For now remote clusters have to be launched using ipcontroller
273 and ipengine separately.
274 """
275 sys.exit(1)
276
259 # Load the remote cluster configuration
277 # Load the remote cluster configuration
260 clConfig = {}
278 clConfig = {}
261 execfile(opt.clusterfile,clConfig)
279 execfile(opt.clusterfile,clConfig)
262 contConfig = clConfig['controller']
280 contConfig = clConfig['controller']
263 engConfig = clConfig['engines']
281 engConfig = clConfig['engines']
264 # Determine where to find sshx:
282 # Determine where to find sshx:
265 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
283 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
266
284
267 # Store all logs inside the ipython directory
285 # Store all logs inside the ipython directory
268 ipdir = cutils.get_ipython_dir()
286 ipdir = get_ipython_dir()
269 pjoin = os.path.join
287 pjoin = os.path.join
270
288
271 logfile = opt.logfile
289 logfile = opt.logfile
272 if logfile is None:
290 if logfile is None:
273 logdir_base = pjoin(ipdir,'log')
291 logdir_base = pjoin(ipdir,'log')
274 ensureDir(logdir_base)
292 ensureDir(logdir_base)
275 logfile = pjoin(logdir_base,'ipcluster')
293 logfile = pjoin(logdir_base,'ipcluster')
276
294
277 # Append this script's PID to the logfile name always
295 # Append this script's PID to the logfile name always
278 logfile = '%s-%s' % (logfile,os.getpid())
296 logfile = '%s-%s' % (logfile,os.getpid())
279
297
280 print 'Starting controller:'
298 print 'Starting controller:'
281 # Controller data:
299 # Controller data:
282 xsys = os.system
300 xsys = os.system
283
301
284 contHost = contConfig['host']
302 contHost = contConfig['host']
285 contLog = '%s-con-%s-' % (logfile,contHost)
303 contLog = '%s-con-%s-' % (logfile,contHost)
286 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
304 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
287 (contHost,sshx,contLog)
305 (contHost,sshx,contLog)
288 #print 'cmd:<%s>' % cmd # dbg
306 #print 'cmd:<%s>' % cmd # dbg
289 xsys(cmd)
307 xsys(cmd)
290 time.sleep(2)
308 time.sleep(2)
291
309
292 print 'Starting engines: '
310 print 'Starting engines: '
293 for engineHost,engineData in engConfig.iteritems():
311 for engineHost,engineData in engConfig.iteritems():
294 if isinstance(engineData,int):
312 if isinstance(engineData,int):
295 numEngines = engineData
313 numEngines = engineData
296 else:
314 else:
297 raise NotImplementedError('port configuration not finished for engines')
315 raise NotImplementedError('port configuration not finished for engines')
298
316
299 print 'Sarting %d engines on %s' % (numEngines,engineHost)
317 print 'Sarting %d engines on %s' % (numEngines,engineHost)
300 engLog = '%s-eng-%s-' % (logfile,engineHost)
318 engLog = '%s-eng-%s-' % (logfile,engineHost)
301 for i in range(numEngines):
319 for i in range(numEngines):
302 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
320 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
303 (engineHost,sshx,contHost,engLog)
321 (engineHost,sshx,contHost,engLog)
304 #print 'cmd:<%s>' % cmd # dbg
322 #print 'cmd:<%s>' % cmd # dbg
305 xsys(cmd)
323 xsys(cmd)
306 # Wait after each host a little bit
324 # Wait after each host a little bit
307 time.sleep(1)
325 time.sleep(1)
308
326
309 startMsg(contConfig['host'])
327 startMsg(contConfig['host'])
310
328
311 def main():
329 def main():
312 """Main driver for the two big options: local or remote cluster."""
330 """Main driver for the two big options: local or remote cluster."""
313
331
332 if sys.platform=='win32':
333 print """ipcluster does not work on Microsoft Windows. Please start
334 your IPython cluster using the ipcontroller and ipengine scripts."""
335 sys.exit(1)
336
314 opt,arg = parse_args()
337 opt,arg = parse_args()
315
338
316 clusterfile = opt.clusterfile
339 clusterfile = opt.clusterfile
317 if clusterfile:
340 if clusterfile:
318 clusterRemote(opt,arg)
341 clusterRemote(opt,arg)
319 else:
342 else:
320 clusterLocal(opt,arg)
343 clusterLocal(opt,arg)
321
344
322
345
323 if __name__=='__main__':
346 if __name__=='__main__':
324 main()
347 main()
@@ -1,171 +1,172 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 from IPython.kernel.config import config_manager as kernel_config_manager
36 from IPython.kernel.config import config_manager as kernel_config_manager
37 from IPython.kernel.engineconnector import EngineConnector
37 from IPython.kernel.engineconnector import EngineConnector
38
38
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Code
41 # Code
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43
43
44 def start_engine():
44 def start_engine():
45 """
45 """
46 Start the engine, by creating it and starting the Twisted reactor.
46 Start the engine, by creating it and starting the Twisted reactor.
47
47
48 This method does:
48 This method does:
49
49
50 * If it exists, runs the `mpi_import_statement` to call `MPI_Init`
50 * If it exists, runs the `mpi_import_statement` to call `MPI_Init`
51 * Starts the engine logging
51 * Starts the engine logging
52 * Creates an IPython shell and wraps it in an `EngineService`
52 * Creates an IPython shell and wraps it in an `EngineService`
53 * Creates a `foolscap.Tub` to use in connecting to a controller.
53 * Creates a `foolscap.Tub` to use in connecting to a controller.
54 * Uses the tub and the `EngineService` along with a Foolscap URL
54 * Uses the tub and the `EngineService` along with a Foolscap URL
55 (or FURL) to connect to the controller and register the engine
55 (or FURL) to connect to the controller and register the engine
56 with the controller
56 with the controller
57 """
57 """
58 kernel_config = kernel_config_manager.get_config_obj()
58 kernel_config = kernel_config_manager.get_config_obj()
59 core_config = core_config_manager.get_config_obj()
59 core_config = core_config_manager.get_config_obj()
60
60
61
61
62 # Execute the mpi import statement that needs to call MPI_Init
62 # Execute the mpi import statement that needs to call MPI_Init
63 global mpi
63 global mpi
64 mpikey = kernel_config['mpi']['default']
64 mpikey = kernel_config['mpi']['default']
65 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
65 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
66 if mpi_import_statement is not None:
66 if mpi_import_statement is not None:
67 try:
67 try:
68 exec mpi_import_statement in globals()
68 exec mpi_import_statement in globals()
69 except:
69 except:
70 mpi = None
70 mpi = None
71 else:
71 else:
72 mpi = None
72 mpi = None
73
73
74 # Start logging
74 # Start logging
75 logfile = kernel_config['engine']['logfile']
75 logfile = kernel_config['engine']['logfile']
76 if logfile:
76 if logfile:
77 logfile = logfile + str(os.getpid()) + '.log'
77 logfile = logfile + str(os.getpid()) + '.log'
78 try:
78 try:
79 openLogFile = open(logfile, 'w')
79 openLogFile = open(logfile, 'w')
80 except:
80 except:
81 openLogFile = sys.stdout
81 openLogFile = sys.stdout
82 else:
82 else:
83 openLogFile = sys.stdout
83 openLogFile = sys.stdout
84 log.startLogging(openLogFile)
84 log.startLogging(openLogFile)
85
85
86 # Create the underlying shell class and EngineService
86 # Create the underlying shell class and EngineService
87 shell_class = import_item(core_config['shell']['shell_class'])
87 shell_class = import_item(core_config['shell']['shell_class'])
88 engine_service = EngineService(shell_class, mpi=mpi)
88 engine_service = EngineService(shell_class, mpi=mpi)
89 shell_import_statement = core_config['shell']['import_statement']
89 shell_import_statement = core_config['shell']['import_statement']
90 if shell_import_statement:
90 if shell_import_statement:
91 try:
91 try:
92 engine_service.execute(shell_import_statement)
92 engine_service.execute(shell_import_statement)
93 except:
93 except:
94 log.msg("Error running import_statement: %s" % sis)
94 log.msg("Error running import_statement: %s" % sis)
95
95
96 # Create the service hierarchy
96 # Create the service hierarchy
97 main_service = service.MultiService()
97 main_service = service.MultiService()
98 engine_service.setServiceParent(main_service)
98 engine_service.setServiceParent(main_service)
99 tub_service = Tub()
99 tub_service = Tub()
100 tub_service.setServiceParent(main_service)
100 tub_service.setServiceParent(main_service)
101 # This needs to be called before the connection is initiated
101 # This needs to be called before the connection is initiated
102 main_service.startService()
102 main_service.startService()
103
103
104 # This initiates the connection to the controller and calls
104 # This initiates the connection to the controller and calls
105 # register_engine to tell the controller we are ready to do work
105 # register_engine to tell the controller we are ready to do work
106 engine_connector = EngineConnector(tub_service)
106 engine_connector = EngineConnector(tub_service)
107 furl_file = kernel_config['engine']['furl_file']
107 furl_file = kernel_config['engine']['furl_file']
108 log.msg("Using furl file: %s" % furl_file)
108 d = engine_connector.connect_to_controller(engine_service, furl_file)
109 d = engine_connector.connect_to_controller(engine_service, furl_file)
109 d.addErrback(lambda _: reactor.stop())
110 d.addErrback(lambda _: reactor.stop())
110
111
111 reactor.run()
112 reactor.run()
112
113
113
114
114 def init_config():
115 def init_config():
115 """
116 """
116 Initialize the configuration using default and command line options.
117 Initialize the configuration using default and command line options.
117 """
118 """
118
119
119 parser = OptionParser()
120 parser = OptionParser()
120
121
121 parser.add_option(
122 parser.add_option(
122 "--furl-file",
123 "--furl-file",
123 type="string",
124 type="string",
124 dest="furl_file",
125 dest="furl_file",
125 help="The filename containing the FURL of the controller"
126 help="The filename containing the FURL of the controller"
126 )
127 )
127 parser.add_option(
128 parser.add_option(
128 "--mpi",
129 "--mpi",
129 type="string",
130 type="string",
130 dest="mpi",
131 dest="mpi",
131 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
132 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
132 )
133 )
133 parser.add_option(
134 parser.add_option(
134 "-l",
135 "-l",
135 "--logfile",
136 "--logfile",
136 type="string",
137 type="string",
137 dest="logfile",
138 dest="logfile",
138 help="log file name (default is stdout)"
139 help="log file name (default is stdout)"
139 )
140 )
140 parser.add_option(
141 parser.add_option(
141 "--ipythondir",
142 "--ipythondir",
142 type="string",
143 type="string",
143 dest="ipythondir",
144 dest="ipythondir",
144 help="look for config files and profiles in this directory"
145 help="look for config files and profiles in this directory"
145 )
146 )
146
147
147 (options, args) = parser.parse_args()
148 (options, args) = parser.parse_args()
148
149
149 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
150 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
150 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
151 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
151
152
152 kernel_config = kernel_config_manager.get_config_obj()
153 kernel_config = kernel_config_manager.get_config_obj()
153 # Now override with command line options
154 # Now override with command line options
154 if options.furl_file is not None:
155 if options.furl_file is not None:
155 kernel_config['engine']['furl_file'] = options.furl_file
156 kernel_config['engine']['furl_file'] = options.furl_file
156 if options.logfile is not None:
157 if options.logfile is not None:
157 kernel_config['engine']['logfile'] = options.logfile
158 kernel_config['engine']['logfile'] = options.logfile
158 if options.mpi is not None:
159 if options.mpi is not None:
159 kernel_config['mpi']['default'] = options.mpi
160 kernel_config['mpi']['default'] = options.mpi
160
161
161
162
162 def main():
163 def main():
163 """
164 """
164 After creating the configuration information, start the engine.
165 After creating the configuration information, start the engine.
165 """
166 """
166 init_config()
167 init_config()
167 start_engine()
168 start_engine()
168
169
169
170
170 if __name__ == "__main__":
171 if __name__ == "__main__":
171 main() No newline at end of file
172 main()
@@ -1,373 +1,373 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Test template for complete engine object"""
3 """Test template for complete engine object"""
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 cPickle as pickle
18 import cPickle as pickle
19
19
20 from twisted.internet import defer, reactor
20 from twisted.internet import defer, reactor
21 from twisted.python import failure
21 from twisted.python import failure
22 from twisted.application import service
22 from twisted.application import service
23 import zope.interface as zi
23 import zope.interface as zi
24
24
25 from IPython.kernel import newserialized
25 from IPython.kernel import newserialized
26 from IPython.kernel import error
26 from IPython.kernel import error
27 from IPython.kernel.pickleutil import can, uncan
27 from IPython.kernel.pickleutil import can, uncan
28 import IPython.kernel.engineservice as es
28 import IPython.kernel.engineservice as es
29 from IPython.kernel.core.interpreter import Interpreter
29 from IPython.kernel.core.interpreter import Interpreter
30 from IPython.testing.parametric import Parametric, parametric
30 from IPython.testing.parametric import Parametric, parametric
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Tests
33 # Tests
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36
36
37 # A sequence of valid commands run through execute
37 # A sequence of valid commands run through execute
38 validCommands = ['a=5',
38 validCommands = ['a=5',
39 'b=10',
39 'b=10',
40 'a=5; b=10; c=a+b',
40 'a=5; b=10; c=a+b',
41 'import math; 2.0*math.pi',
41 'import math; 2.0*math.pi',
42 """def f():
42 """def f():
43 result = 0.0
43 result = 0.0
44 for i in range(10):
44 for i in range(10):
45 result += i
45 result += i
46 """,
46 """,
47 'if 1<2: a=5',
47 'if 1<2: a=5',
48 """import time
48 """import time
49 time.sleep(0.1)""",
49 time.sleep(0.1)""",
50 """from math import cos;
50 """from math import cos;
51 x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded
51 x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded
52 """from sets import Set
52 """from sets import Set
53 s = Set()
53 s = Set()
54 """, # Trailing whitespace should be allowed.
54 """, # Trailing whitespace should be allowed.
55 """import math
55 """import math
56 math.cos(1.0)""", # Test a method call with a discarded return value
56 math.cos(1.0)""", # Test a method call with a discarded return value
57 """x=1.0234
57 """x=1.0234
58 a=5; b=10""", # Test an embedded semicolon
58 a=5; b=10""", # Test an embedded semicolon
59 """x=1.0234
59 """x=1.0234
60 a=5; b=10;""" # Test both an embedded and trailing semicolon
60 a=5; b=10;""" # Test both an embedded and trailing semicolon
61 ]
61 ]
62
62
63 # A sequence of commands that raise various exceptions
63 # A sequence of commands that raise various exceptions
64 invalidCommands = [('a=1/0',ZeroDivisionError),
64 invalidCommands = [('a=1/0',ZeroDivisionError),
65 ('print v',NameError),
65 ('print v',NameError),
66 ('l=[];l[0]',IndexError),
66 ('l=[];l[0]',IndexError),
67 ("d={};d['a']",KeyError),
67 ("d={};d['a']",KeyError),
68 ("assert 1==0",AssertionError),
68 ("assert 1==0",AssertionError),
69 ("import abababsdbfsbaljasdlja",ImportError),
69 ("import abababsdbfsbaljasdlja",ImportError),
70 ("raise Exception()",Exception)]
70 ("raise Exception()",Exception)]
71
71
72 def testf(x):
72 def testf(x):
73 return 2.0*x
73 return 2.0*x
74
74
75 globala = 99
75 globala = 99
76
76
77 def testg(x):
77 def testg(x):
78 return globala*x
78 return globala*x
79
79
80 class IEngineCoreTestCase(object):
80 class IEngineCoreTestCase(object):
81 """Test an IEngineCore implementer."""
81 """Test an IEngineCore implementer."""
82
82
83 def createShell(self):
83 def createShell(self):
84 return Interpreter()
84 return Interpreter()
85
85
86 def catchQueueCleared(self, f):
86 def catchQueueCleared(self, f):
87 try:
87 try:
88 f.raiseException()
88 f.raiseException()
89 except error.QueueCleared:
89 except error.QueueCleared:
90 pass
90 pass
91
91
92 def testIEngineCoreInterface(self):
92 def testIEngineCoreInterface(self):
93 """Does self.engine claim to implement IEngineCore?"""
93 """Does self.engine claim to implement IEngineCore?"""
94 self.assert_(es.IEngineCore.providedBy(self.engine))
94 self.assert_(es.IEngineCore.providedBy(self.engine))
95
95
96 def testIEngineCoreInterfaceMethods(self):
96 def testIEngineCoreInterfaceMethods(self):
97 """Does self.engine have the methods and attributes in IEngineCore."""
97 """Does self.engine have the methods and attributes in IEngineCore."""
98 for m in list(es.IEngineCore):
98 for m in list(es.IEngineCore):
99 self.assert_(hasattr(self.engine, m))
99 self.assert_(hasattr(self.engine, m))
100
100
101 def testIEngineCoreDeferreds(self):
101 def testIEngineCoreDeferreds(self):
102 d = self.engine.execute('a=5')
102 d = self.engine.execute('a=5')
103 d.addCallback(lambda _: self.engine.pull('a'))
103 d.addCallback(lambda _: self.engine.pull('a'))
104 d.addCallback(lambda _: self.engine.get_result())
104 d.addCallback(lambda _: self.engine.get_result())
105 d.addCallback(lambda _: self.engine.keys())
105 d.addCallback(lambda _: self.engine.keys())
106 d.addCallback(lambda _: self.engine.push(dict(a=10)))
106 d.addCallback(lambda _: self.engine.push(dict(a=10)))
107 return d
107 return d
108
108
109 def runTestExecute(self, cmd):
109 def runTestExecute(self, cmd):
110 self.shell = Interpreter()
110 self.shell = Interpreter()
111 actual = self.shell.execute(cmd)
111 actual = self.shell.execute(cmd)
112 def compare(computed):
112 def compare(computed):
113 actual['id'] = computed['id']
113 actual['id'] = computed['id']
114 self.assertEquals(actual, computed)
114 self.assertEquals(actual, computed)
115 d = self.engine.execute(cmd)
115 d = self.engine.execute(cmd)
116 d.addCallback(compare)
116 d.addCallback(compare)
117 return d
117 return d
118
118
119 @parametric
119 @parametric
120 def testExecute(cls):
120 def testExecute(cls):
121 return [(cls.runTestExecute, cmd) for cmd in validCommands]
121 return [(cls.runTestExecute, cmd) for cmd in validCommands]
122
122
123 def runTestExecuteFailures(self, cmd, exc):
123 def runTestExecuteFailures(self, cmd, exc):
124 def compare(f):
124 def compare(f):
125 self.assertRaises(exc, f.raiseException)
125 self.assertRaises(exc, f.raiseException)
126 d = self.engine.execute(cmd)
126 d = self.engine.execute(cmd)
127 d.addErrback(compare)
127 d.addErrback(compare)
128 return d
128 return d
129
129
130 @parametric
130 @parametric
131 def testExecuteFailuresEngineService(cls):
131 def testExecuteFailuresEngineService(cls):
132 return [(cls.runTestExecuteFailures, cmd, exc)
132 return [(cls.runTestExecuteFailures, cmd, exc)
133 for cmd, exc in invalidCommands]
133 for cmd, exc in invalidCommands]
134
134
135 def runTestPushPull(self, o):
135 def runTestPushPull(self, o):
136 d = self.engine.push(dict(a=o))
136 d = self.engine.push(dict(a=o))
137 d.addCallback(lambda r: self.engine.pull('a'))
137 d.addCallback(lambda r: self.engine.pull('a'))
138 d.addCallback(lambda r: self.assertEquals(o,r))
138 d.addCallback(lambda r: self.assertEquals(o,r))
139 return d
139 return d
140
140
141 @parametric
141 @parametric
142 def testPushPull(cls):
142 def testPushPull(cls):
143 objs = [10,"hi there",1.2342354,{"p":(1,2)},None]
143 objs = [10,"hi there",1.2342354,{"p":(1,2)},None]
144 return [(cls.runTestPushPull, o) for o in objs]
144 return [(cls.runTestPushPull, o) for o in objs]
145
145
146 def testPullNameError(self):
146 def testPullNameError(self):
147 d = self.engine.push(dict(a=5))
147 d = self.engine.push(dict(a=5))
148 d.addCallback(lambda _:self.engine.reset())
148 d.addCallback(lambda _:self.engine.reset())
149 d.addCallback(lambda _: self.engine.pull("a"))
149 d.addCallback(lambda _: self.engine.pull("a"))
150 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
150 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
151 return d
151 return d
152
152
153 def testPushPullFailures(self):
153 def testPushPullFailures(self):
154 d = self.engine.pull('a')
154 d = self.engine.pull('a')
155 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
155 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
156 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
156 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
157 d.addCallback(lambda _: self.engine.pull('l'))
157 d.addCallback(lambda _: self.engine.pull('l'))
158 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
158 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
159 d.addCallback(lambda _: self.engine.push(dict(l=lambda x: x)))
159 d.addCallback(lambda _: self.engine.push(dict(l=lambda x: x)))
160 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
160 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
161 return d
161 return d
162
162
163 def testPushPullArray(self):
163 def testPushPullArray(self):
164 try:
164 try:
165 import numpy
165 import numpy
166 except:
166 except:
167 return
167 return
168 a = numpy.random.random(1000)
168 a = numpy.random.random(1000)
169 d = self.engine.push(dict(a=a))
169 d = self.engine.push(dict(a=a))
170 d.addCallback(lambda _: self.engine.pull('a'))
170 d.addCallback(lambda _: self.engine.pull('a'))
171 d.addCallback(lambda b: b==a)
171 d.addCallback(lambda b: b==a)
172 d.addCallback(lambda c: c.all())
172 d.addCallback(lambda c: c.all())
173 return self.assertDeferredEquals(d, True)
173 return self.assertDeferredEquals(d, True)
174
174
175 def testPushFunction(self):
175 def testPushFunction(self):
176
176
177 d = self.engine.push_function(dict(f=testf))
177 d = self.engine.push_function(dict(f=testf))
178 d.addCallback(lambda _: self.engine.execute('result = f(10)'))
178 d.addCallback(lambda _: self.engine.execute('result = f(10)'))
179 d.addCallback(lambda _: self.engine.pull('result'))
179 d.addCallback(lambda _: self.engine.pull('result'))
180 d.addCallback(lambda r: self.assertEquals(r, testf(10)))
180 d.addCallback(lambda r: self.assertEquals(r, testf(10)))
181 return d
181 return d
182
182
183 def testPullFunction(self):
183 def testPullFunction(self):
184 d = self.engine.push_function(dict(f=testf, g=testg))
184 d = self.engine.push_function(dict(f=testf, g=testg))
185 d.addCallback(lambda _: self.engine.pull_function(('f','g')))
185 d.addCallback(lambda _: self.engine.pull_function(('f','g')))
186 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
186 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
187 return d
187 return d
188
188
189 def testPushFunctionGlobal(self):
189 def testPushFunctionGlobal(self):
190 """Make sure that pushed functions pick up the user's namespace for globals."""
190 """Make sure that pushed functions pick up the user's namespace for globals."""
191 d = self.engine.push(dict(globala=globala))
191 d = self.engine.push(dict(globala=globala))
192 d.addCallback(lambda _: self.engine.push_function(dict(g=testg)))
192 d.addCallback(lambda _: self.engine.push_function(dict(g=testg)))
193 d.addCallback(lambda _: self.engine.execute('result = g(10)'))
193 d.addCallback(lambda _: self.engine.execute('result = g(10)'))
194 d.addCallback(lambda _: self.engine.pull('result'))
194 d.addCallback(lambda _: self.engine.pull('result'))
195 d.addCallback(lambda r: self.assertEquals(r, testg(10)))
195 d.addCallback(lambda r: self.assertEquals(r, testg(10)))
196 return d
196 return d
197
197
198 def testGetResultFailure(self):
198 def testGetResultFailure(self):
199 d = self.engine.get_result(None)
199 d = self.engine.get_result(None)
200 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
200 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
201 d.addCallback(lambda _: self.engine.get_result(10))
201 d.addCallback(lambda _: self.engine.get_result(10))
202 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
202 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
203 return d
203 return d
204
204
205 def runTestGetResult(self, cmd):
205 def runTestGetResult(self, cmd):
206 self.shell = Interpreter()
206 self.shell = Interpreter()
207 actual = self.shell.execute(cmd)
207 actual = self.shell.execute(cmd)
208 def compare(computed):
208 def compare(computed):
209 actual['id'] = computed['id']
209 actual['id'] = computed['id']
210 self.assertEquals(actual, computed)
210 self.assertEquals(actual, computed)
211 d = self.engine.execute(cmd)
211 d = self.engine.execute(cmd)
212 d.addCallback(lambda r: self.engine.get_result(r['number']))
212 d.addCallback(lambda r: self.engine.get_result(r['number']))
213 d.addCallback(compare)
213 d.addCallback(compare)
214 return d
214 return d
215
215
216 @parametric
216 @parametric
217 def testGetResult(cls):
217 def testGetResult(cls):
218 return [(cls.runTestGetResult, cmd) for cmd in validCommands]
218 return [(cls.runTestGetResult, cmd) for cmd in validCommands]
219
219
220 def testGetResultDefault(self):
220 def testGetResultDefault(self):
221 cmd = 'a=5'
221 cmd = 'a=5'
222 shell = self.createShell()
222 shell = self.createShell()
223 shellResult = shell.execute(cmd)
223 shellResult = shell.execute(cmd)
224 def popit(dikt, key):
224 def popit(dikt, key):
225 dikt.pop(key)
225 dikt.pop(key)
226 return dikt
226 return dikt
227 d = self.engine.execute(cmd)
227 d = self.engine.execute(cmd)
228 d.addCallback(lambda _: self.engine.get_result())
228 d.addCallback(lambda _: self.engine.get_result())
229 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r,'id')))
229 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r,'id')))
230 return d
230 return d
231
231
232 def testKeys(self):
232 def testKeys(self):
233 d = self.engine.keys()
233 d = self.engine.keys()
234 d.addCallback(lambda s: isinstance(s, list))
234 d.addCallback(lambda s: isinstance(s, list))
235 d.addCallback(lambda r: self.assertEquals(r, True))
235 d.addCallback(lambda r: self.assertEquals(r, True))
236 return d
236 return d
237
237
238 Parametric(IEngineCoreTestCase)
238 Parametric(IEngineCoreTestCase)
239
239
240 class IEngineSerializedTestCase(object):
240 class IEngineSerializedTestCase(object):
241 """Test an IEngineCore implementer."""
241 """Test an IEngineCore implementer."""
242
242
243 def testIEngineSerializedInterface(self):
243 def testIEngineSerializedInterface(self):
244 """Does self.engine claim to implement IEngineCore?"""
244 """Does self.engine claim to implement IEngineCore?"""
245 self.assert_(es.IEngineSerialized.providedBy(self.engine))
245 self.assert_(es.IEngineSerialized.providedBy(self.engine))
246
246
247 def testIEngineSerializedInterfaceMethods(self):
247 def testIEngineSerializedInterfaceMethods(self):
248 """Does self.engine have the methods and attributes in IEngireCore."""
248 """Does self.engine have the methods and attributes in IEngineCore."""
249 for m in list(es.IEngineSerialized):
249 for m in list(es.IEngineSerialized):
250 self.assert_(hasattr(self.engine, m))
250 self.assert_(hasattr(self.engine, m))
251
251
252 def testIEngineSerializedDeferreds(self):
252 def testIEngineSerializedDeferreds(self):
253 dList = []
253 dList = []
254 d = self.engine.push_serialized(dict(key=newserialized.serialize(12345)))
254 d = self.engine.push_serialized(dict(key=newserialized.serialize(12345)))
255 self.assert_(isinstance(d, defer.Deferred))
255 self.assert_(isinstance(d, defer.Deferred))
256 dList.append(d)
256 dList.append(d)
257 d = self.engine.pull_serialized('key')
257 d = self.engine.pull_serialized('key')
258 self.assert_(isinstance(d, defer.Deferred))
258 self.assert_(isinstance(d, defer.Deferred))
259 dList.append(d)
259 dList.append(d)
260 D = defer.DeferredList(dList)
260 D = defer.DeferredList(dList)
261 return D
261 return D
262
262
263 def testPushPullSerialized(self):
263 def testPushPullSerialized(self):
264 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
264 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
265 d = defer.succeed(None)
265 d = defer.succeed(None)
266 for o in objs:
266 for o in objs:
267 self.engine.push_serialized(dict(key=newserialized.serialize(o)))
267 self.engine.push_serialized(dict(key=newserialized.serialize(o)))
268 value = self.engine.pull_serialized('key')
268 value = self.engine.pull_serialized('key')
269 value.addCallback(lambda serial: newserialized.IUnSerialized(serial).getObject())
269 value.addCallback(lambda serial: newserialized.IUnSerialized(serial).getObject())
270 d = self.assertDeferredEquals(value,o,d)
270 d = self.assertDeferredEquals(value,o,d)
271 return d
271 return d
272
272
273 def testPullSerializedFailures(self):
273 def testPullSerializedFailures(self):
274 d = self.engine.pull_serialized('a')
274 d = self.engine.pull_serialized('a')
275 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
275 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
276 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
276 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
277 d.addCallback(lambda _: self.engine.pull_serialized('l'))
277 d.addCallback(lambda _: self.engine.pull_serialized('l'))
278 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
278 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
279 return d
279 return d
280
280
281 Parametric(IEngineSerializedTestCase)
281 Parametric(IEngineSerializedTestCase)
282
282
283 class IEngineQueuedTestCase(object):
283 class IEngineQueuedTestCase(object):
284 """Test an IEngineQueued implementer."""
284 """Test an IEngineQueued implementer."""
285
285
286 def testIEngineQueuedInterface(self):
286 def testIEngineQueuedInterface(self):
287 """Does self.engine claim to implement IEngineQueued?"""
287 """Does self.engine claim to implement IEngineQueued?"""
288 self.assert_(es.IEngineQueued.providedBy(self.engine))
288 self.assert_(es.IEngineQueued.providedBy(self.engine))
289
289
290 def testIEngineQueuedInterfaceMethods(self):
290 def testIEngineQueuedInterfaceMethods(self):
291 """Does self.engine have the methods and attributes in IEngireQueued."""
291 """Does self.engine have the methods and attributes in IEngineQueued."""
292 for m in list(es.IEngineQueued):
292 for m in list(es.IEngineQueued):
293 self.assert_(hasattr(self.engine, m))
293 self.assert_(hasattr(self.engine, m))
294
294
295 def testIEngineQueuedDeferreds(self):
295 def testIEngineQueuedDeferreds(self):
296 dList = []
296 dList = []
297 d = self.engine.clear_queue()
297 d = self.engine.clear_queue()
298 self.assert_(isinstance(d, defer.Deferred))
298 self.assert_(isinstance(d, defer.Deferred))
299 dList.append(d)
299 dList.append(d)
300 d = self.engine.queue_status()
300 d = self.engine.queue_status()
301 self.assert_(isinstance(d, defer.Deferred))
301 self.assert_(isinstance(d, defer.Deferred))
302 dList.append(d)
302 dList.append(d)
303 D = defer.DeferredList(dList)
303 D = defer.DeferredList(dList)
304 return D
304 return D
305
305
306 def testClearQueue(self):
306 def testClearQueue(self):
307 result = self.engine.clear_queue()
307 result = self.engine.clear_queue()
308 d1 = self.assertDeferredEquals(result, None)
308 d1 = self.assertDeferredEquals(result, None)
309 d1.addCallback(lambda _: self.engine.queue_status())
309 d1.addCallback(lambda _: self.engine.queue_status())
310 d2 = self.assertDeferredEquals(d1, {'queue':[], 'pending':'None'})
310 d2 = self.assertDeferredEquals(d1, {'queue':[], 'pending':'None'})
311 return d2
311 return d2
312
312
313 def testQueueStatus(self):
313 def testQueueStatus(self):
314 result = self.engine.queue_status()
314 result = self.engine.queue_status()
315 result.addCallback(lambda r: 'queue' in r and 'pending' in r)
315 result.addCallback(lambda r: 'queue' in r and 'pending' in r)
316 d = self.assertDeferredEquals(result, True)
316 d = self.assertDeferredEquals(result, True)
317 return d
317 return d
318
318
319 Parametric(IEngineQueuedTestCase)
319 Parametric(IEngineQueuedTestCase)
320
320
321 class IEnginePropertiesTestCase(object):
321 class IEnginePropertiesTestCase(object):
322 """Test an IEngineProperties implementor."""
322 """Test an IEngineProperties implementor."""
323
323
324 def testIEnginePropertiesInterface(self):
324 def testIEnginePropertiesInterface(self):
325 """Does self.engine claim to implement IEngineProperties?"""
325 """Does self.engine claim to implement IEngineProperties?"""
326 self.assert_(es.IEngineProperties.providedBy(self.engine))
326 self.assert_(es.IEngineProperties.providedBy(self.engine))
327
327
328 def testIEnginePropertiesInterfaceMethods(self):
328 def testIEnginePropertiesInterfaceMethods(self):
329 """Does self.engine have the methods and attributes in IEngireProperties."""
329 """Does self.engine have the methods and attributes in IEngineProperties."""
330 for m in list(es.IEngineProperties):
330 for m in list(es.IEngineProperties):
331 self.assert_(hasattr(self.engine, m))
331 self.assert_(hasattr(self.engine, m))
332
332
333 def testGetSetProperties(self):
333 def testGetSetProperties(self):
334 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
334 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
335 d = self.engine.set_properties(dikt)
335 d = self.engine.set_properties(dikt)
336 d.addCallback(lambda r: self.engine.get_properties())
336 d.addCallback(lambda r: self.engine.get_properties())
337 d = self.assertDeferredEquals(d, dikt)
337 d = self.assertDeferredEquals(d, dikt)
338 d.addCallback(lambda r: self.engine.get_properties(('c',)))
338 d.addCallback(lambda r: self.engine.get_properties(('c',)))
339 d = self.assertDeferredEquals(d, {'c': dikt['c']})
339 d = self.assertDeferredEquals(d, {'c': dikt['c']})
340 d.addCallback(lambda r: self.engine.set_properties(dict(c=False)))
340 d.addCallback(lambda r: self.engine.set_properties(dict(c=False)))
341 d.addCallback(lambda r: self.engine.get_properties(('c', 'd')))
341 d.addCallback(lambda r: self.engine.get_properties(('c', 'd')))
342 d = self.assertDeferredEquals(d, dict(c=False, d=None))
342 d = self.assertDeferredEquals(d, dict(c=False, d=None))
343 return d
343 return d
344
344
345 def testClearProperties(self):
345 def testClearProperties(self):
346 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
346 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
347 d = self.engine.set_properties(dikt)
347 d = self.engine.set_properties(dikt)
348 d.addCallback(lambda r: self.engine.clear_properties())
348 d.addCallback(lambda r: self.engine.clear_properties())
349 d.addCallback(lambda r: self.engine.get_properties())
349 d.addCallback(lambda r: self.engine.get_properties())
350 d = self.assertDeferredEquals(d, {})
350 d = self.assertDeferredEquals(d, {})
351 return d
351 return d
352
352
353 def testDelHasProperties(self):
353 def testDelHasProperties(self):
354 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
354 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
355 d = self.engine.set_properties(dikt)
355 d = self.engine.set_properties(dikt)
356 d.addCallback(lambda r: self.engine.del_properties(('b','e')))
356 d.addCallback(lambda r: self.engine.del_properties(('b','e')))
357 d.addCallback(lambda r: self.engine.has_properties(('a','b','c','d','e')))
357 d.addCallback(lambda r: self.engine.has_properties(('a','b','c','d','e')))
358 d = self.assertDeferredEquals(d, [True, False, True, True, False])
358 d = self.assertDeferredEquals(d, [True, False, True, True, False])
359 return d
359 return d
360
360
361 def testStrictDict(self):
361 def testStrictDict(self):
362 s = """from IPython.kernel.engineservice import get_engine
362 s = """from IPython.kernel.engineservice import get_engine
363 p = get_engine(%s).properties"""%self.engine.id
363 p = get_engine(%s).properties"""%self.engine.id
364 d = self.engine.execute(s)
364 d = self.engine.execute(s)
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
366 d = self.assertDeferredRaises(d, error.InvalidProperty)
366 d = self.assertDeferredRaises(d, error.InvalidProperty)
367 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
367 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.get_properties('a'))
369 d.addCallback(lambda r: self.engine.get_properties('a'))
370 d = self.assertDeferredEquals(d, dict(a=range(5)))
370 d = self.assertDeferredEquals(d, dict(a=range(5)))
371 return d
371 return d
372
372
373 Parametric(IEnginePropertiesTestCase)
373 Parametric(IEnginePropertiesTestCase)
@@ -1,75 +1,74 b''
1 # Set this prefix to where you want to install the plugin
1 # Set this prefix to where you want to install the plugin
2 PREFIX=~/usr/local
2 PREFIX=/usr/local
3 PREFIX=~/tmp/local
4
3
5 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
4 NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors
6 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \
5 NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \
7 --detailed-errors
6 --detailed-errors
8
7
9 SRC=ipdoctest.py setup.py ../decorators.py
8 SRC=ipdoctest.py setup.py ../decorators.py
10
9
11 # Default target for clean 'make'
10 # Default target for clean 'make'
12 default: iplib
11 default: iplib
13
12
14 # The actual plugin installation
13 # The actual plugin installation
15 plugin: IPython_doctest_plugin.egg-info
14 plugin: IPython_doctest_plugin.egg-info
16
15
17 # Simple targets that test one thing
16 # Simple targets that test one thing
18 simple: plugin simple.py
17 simple: plugin simple.py
19 $(NOSE) simple.py
18 $(NOSE) simple.py
20
19
21 dtest: plugin dtexample.py
20 dtest: plugin dtexample.py
22 $(NOSE) dtexample.py
21 $(NOSE) dtexample.py
23
22
24 rtest: plugin test_refs.py
23 rtest: plugin test_refs.py
25 $(NOSE) test_refs.py
24 $(NOSE) test_refs.py
26
25
27 test: plugin dtexample.py
26 test: plugin dtexample.py
28 $(NOSE) dtexample.py test*.py test*.txt
27 $(NOSE) dtexample.py test*.py test*.txt
29
28
30 deb: plugin dtexample.py
29 deb: plugin dtexample.py
31 $(NOSE) test_combo.txt
30 $(NOSE) test_combo.txt
32
31
33 # IPython tests
32 # IPython tests
34 deco:
33 deco:
35 $(NOSE0) IPython.testing.decorators
34 $(NOSE0) IPython.testing.decorators
36
35
37 magic: plugin
36 magic: plugin
38 $(NOSE) IPython.Magic
37 $(NOSE) IPython.Magic
39
38
40 ipipe: plugin
39 ipipe: plugin
41 $(NOSE) IPython.Extensions.ipipe
40 $(NOSE) IPython.Extensions.ipipe
42
41
43 iplib: plugin
42 iplib: plugin
44 $(NOSE) IPython.iplib
43 $(NOSE) IPython.iplib
45
44
46 strd: plugin
45 strd: plugin
47 $(NOSE) IPython.strdispatch
46 $(NOSE) IPython.strdispatch
48
47
49 engine: plugin
48 engine: plugin
50 $(NOSE) IPython.kernel
49 $(NOSE) IPython.kernel
51
50
52 tf: plugin
51 tf: plugin
53 $(NOSE) IPython.config.traitlets
52 $(NOSE) IPython.config.traitlets
54
53
55 # All of ipython itself
54 # All of ipython itself
56 ipython: plugin
55 ipython: plugin
57 $(NOSE) IPython
56 $(NOSE) IPython
58
57
59
58
60 # Combined targets
59 # Combined targets
61 sr: rtest strd
60 sr: rtest strd
62
61
63 base: dtest rtest test strd deco
62 base: dtest rtest test strd deco
64
63
65 quick: base iplib ipipe
64 quick: base iplib ipipe
66
65
67 all: base ipython
66 all: base ipython
68
67
69 # Main plugin and cleanup
68 # Main plugin and cleanup
70 IPython_doctest_plugin.egg-info: $(SRC)
69 IPython_doctest_plugin.egg-info: $(SRC)
71 python setup.py install --prefix=$(PREFIX)
70 python setup.py install --prefix=$(PREFIX)
72 touch $@
71 touch $@
73
72
74 clean:
73 clean:
75 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
74 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
@@ -1,42 +1,43 b''
1 import numpy as N
1 import numpy as N
2 from math import *
2 from math import *
3
3
4 class MCOptionPricer(object):
4 class MCOptionPricer(object):
5 def __init__(self, S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000):
5 def __init__(self, S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000):
6 self.S = S
6 self.S = S
7 self.K = K
7 self.K = K
8 self.sigma = sigma
8 self.sigma = sigma
9 self.r = r
9 self.r = r
10 self.days = days
10 self.days = days
11 self.paths = paths
11 self.paths = paths
12 self.h = 1.0/self.days
12 self.h = 1.0/self.days
13 self.const1 = exp((self.r-0.5*self.sigma**2)*self.h)
13 self.const1 = exp((self.r-0.5*self.sigma**2)*self.h)
14 self.const2 = self.sigma*sqrt(self.h)
14 self.const2 = self.sigma*sqrt(self.h)
15
15
16 def run(self):
16 def run(self):
17 stock_price = self.S*N.ones(self.paths, dtype='float64')
17 stock_price = self.S*N.ones(self.paths, dtype='float64')
18 stock_price_sum = N.zeros(self.paths, dtype='float64')
18 stock_price_sum = N.zeros(self.paths, dtype='float64')
19 for j in range(self.days):
19 for j in range(self.days):
20 growth_factor = self.const1*N.exp(self.const2*N.random.standard_normal(self.paths))
20 growth_factor = self.const1*N.exp(self.const2*N.random.standard_normal(self.paths))
21 stock_price = stock_price*growth_factor
21 stock_price = stock_price*growth_factor
22 stock_price_sum = stock_price_sum + stock_price
22 stock_price_sum = stock_price_sum + stock_price
23 stock_price_avg = stock_price_sum/self.days
23 stock_price_avg = stock_price_sum/self.days
24 zeros = N.zeros(self.paths, dtype='float64')
24 zeros = N.zeros(self.paths, dtype='float64')
25 r_factor = exp(-self.r*self.h*self.days)
25 r_factor = exp(-self.r*self.h*self.days)
26 self.vanilla_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price))
26 self.vanilla_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price))
27 self.asian_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price_avg))
27 self.asian_put = r_factor*N.mean(N.maximum(zeros,self.K-stock_price_avg))
28 self.vanilla_call = r_factor*N.mean(N.maximum(zeros,stock_price-self.K))
28 self.vanilla_call = r_factor*N.mean(N.maximum(zeros,stock_price-self.K))
29 self.asian_call = r_factor*N.mean(N.maximum(zeros,stock_price_avg-self.K))
29 self.asian_call = r_factor*N.mean(N.maximum(zeros,stock_price_avg-self.K))
30
30
31
31
32 def main():
32 def main():
33 op = MCOptionPricer()
33 op = MCOptionPricer()
34 op.run()
34 op.run()
35 print "Vanilla Put Price = ", op.vanilla_put
35 print "Vanilla Put Price = ", op.vanilla_put
36 print "Asian Put Price = ", op.asian_put
36 print "Asian Put Price = ", op.asian_put
37 print "Vanilla Call Price = ", op.vanilla_call
37 print "Vanilla Call Price = ", op.vanilla_call
38 print "Asian Call Price = ", op.asian_call
38 print "Asian Call Price = ", op.asian_call
39
39
40
40
41 if __name__ == '__main__':
41 if __name__ == '__main__':
42 main()
42 main()
43
@@ -1,57 +1,57 b''
1 #-------------------------------------------------------------------------------
1 #-------------------------------------------------------------------------------
2 # Driver code that the client runs.
2 # Driver code that the client runs.
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # To run this code start a controller and engines using:
4 # To run this code start a controller and engines using:
5 # ipcluster -n 2
5 # ipcluster -n 2
6 # Then run the scripts by doing irunner rmt.ipy or by starting ipython and
6 # Then run the scripts by doing irunner rmt.ipy or by starting ipython and
7 # doing run rmt.ipy.
7 # doing run rmt.ipy.
8
8
9 from rmtkernel import *
9 from rmtkernel import *
10 from IPython.kernel import client
10 from IPython.kernel import client
11
11
12
12
13 def wignerDistribution(s):
13 def wignerDistribution(s):
14 """Returns (s, rho(s)) for the Wigner GOE distribution."""
14 """Returns (s, rho(s)) for the Wigner GOE distribution."""
15 return (numpy.pi*s/2.0) * numpy.exp(-numpy.pi*s**2/4.)
15 return (numpy.pi*s/2.0) * numpy.exp(-numpy.pi*s**2/4.)
16
16
17
17
18 def generateWignerData():
18 def generateWignerData():
19 s = numpy.linspace(0.0,4.0,400)
19 s = numpy.linspace(0.0,4.0,400)
20 rhos = wignerDistribution(s)
20 rhos = wignerDistribution(s)
21 return s, rhos
21 return s, rhos
22
22
23
23
24 def serialDiffs(num, N):
24 def serialDiffs(num, N):
25 diffs = ensembleDiffs(num, N)
25 diffs = ensembleDiffs(num, N)
26 normalizedDiffs = normalizeDiffs(diffs)
26 normalizedDiffs = normalizeDiffs(diffs)
27 return normalizedDiffs
27 return normalizedDiffs
28
28
29
29
30 def parallelDiffs(rc, num, N):
30 def parallelDiffs(rc, num, N):
31 nengines = len(rc.get_ids())
31 nengines = len(rc.get_ids())
32 num_per_engine = num/nengines
32 num_per_engine = num/nengines
33 print "Running with", num_per_engine, "per engine."
33 print "Running with", num_per_engine, "per engine."
34 rc.push(dict(num_per_engine=num_per_engine, N=N))
34 rc.push(dict(num_per_engine=num_per_engine, N=N))
35 rc.execute('diffs = ensembleDiffs(num_per_engine, N)')
35 rc.execute('diffs = ensembleDiffs(num_per_engine, N)')
36 # gather blocks always for now
36 # gather blocks always for now
37 pr = rc.gather('diffs')
37 pr = rc.gather('diffs')
38 return pr.r
38 return pr.r
39
39
40
40
41 # Main code
41 # Main code
42 if __name__ == '__main__':
42 if __name__ == '__main__':
43 rc = client.MultiEngineClient()
43 rc = client.MultiEngineClient()
44 print "Distributing code to engines..."
44 print "Distributing code to engines..."
45 r = rc.run('rmtkernel.py')
45 r = rc.run('rmtkernel.py')
46 rc.block = False
46 rc.block = False
47
47
48 # Simulation parameters
48 # Simulation parameters
49 nmats = 100
49 nmats = 100
50 matsize = 30
50 matsize = 30
51
51
52 %timeit -n1 -r1 serialDiffs(nmats,matsize)
52 %timeit -n1 -r1 serialDiffs(nmats,matsize)
53 %timeit -n1 -r1 parallelDiffs(rc, nmats, matsize)
53 %timeit -n1 -r1 parallelDiffs(rc, nmats, matsize)
54
54
55 # Uncomment these to plot the histogram
55 # Uncomment these to plot the histogram
56 import pylab
56 # import pylab
57 pylab.hist(parallelDiffs(rc,matsize,matsize))
57 # pylab.hist(parallelDiffs(rc,matsize,matsize))
@@ -1,89 +1,106 b''
1 #!python
1 #!python
2 """Windows-specific part of the installation"""
2 """Windows-specific part of the installation"""
3
3
4 import os, sys, shutil
4 import os, sys, shutil
5 pjoin = os.path.join
5
6
6 def mkshortcut(target,description,link_file,*args,**kw):
7 def mkshortcut(target,description,link_file,*args,**kw):
7 """make a shortcut if it doesn't exist, and register its creation"""
8 """make a shortcut if it doesn't exist, and register its creation"""
8
9
9 create_shortcut(target, description, link_file,*args,**kw)
10 create_shortcut(target, description, link_file,*args,**kw)
10 file_created(link_file)
11 file_created(link_file)
11
12
12 def install():
13 def install():
13 """Routine to be run by the win32 installer with the -install switch."""
14 """Routine to be run by the win32 installer with the -install switch."""
14
15
15 from IPython.Release import version
16 from IPython.Release import version
16
17
17 # Get some system constants
18 # Get some system constants
18 prefix = sys.prefix
19 prefix = sys.prefix
19 python = prefix + r'\python.exe'
20 python = pjoin(prefix, 'python.exe')
20 # Lookup path to common startmenu ...
21 ip_dir = get_special_folder_path('CSIDL_COMMON_PROGRAMS') + r'\IPython'
22
21
23 # Some usability warnings at installation time. I don't want them at the
22 # Lookup path to common startmenu ...
24 # top-level, so they don't appear if the user is uninstalling.
23 ip_start_menu = pjoin(get_special_folder_path('CSIDL_COMMON_PROGRAMS'), 'IPython')
25 try:
26 import ctypes
27 except ImportError:
28 print ('To take full advantage of IPython, you need ctypes from:\n'
29 'http://sourceforge.net/projects/ctypes')
30
31 try:
32 import win32con
33 except ImportError:
34 print ('To take full advantage of IPython, you need pywin32 from:\n'
35 'http://starship.python.net/crew/mhammond/win32/Downloads.html')
36
37 try:
38 import readline
39 except ImportError:
40 print ('To take full advantage of IPython, you need readline from:\n'
41 'https://launchpad.net/pyreadline')
42
43 ipybase = '"' + prefix + r'\scripts\ipython"'
44 # Create IPython entry ...
24 # Create IPython entry ...
45 if not os.path.isdir(ip_dir):
25 if not os.path.isdir(ip_start_menu):
46 os.mkdir(ip_dir)
26 os.mkdir(ip_start_menu)
47 directory_created(ip_dir)
27 directory_created(ip_start_menu)
48
28
49 # Create program shortcuts ...
29 # Create .py and .bat files to make things available from
50 f = ip_dir + r'\IPython.lnk'
30 # the Windows command line. Thanks to the Twisted project
51 a = ipybase
31 # for this logic!
52 mkshortcut(python,'IPython',f,a)
32 programs = [
53
33 'ipython',
54 f = ip_dir + r'\pysh.lnk'
34 'iptest',
55 a = ipybase+' -p sh'
35 'ipcontroller',
56 mkshortcut(python,'IPython (command prompt mode)',f,a)
36 'ipengine',
57
37 'ipcluster',
58 f = ip_dir + r'\pylab.lnk'
38 'ipythonx',
59 a = ipybase+' -pylab'
39 'ipython-wx',
60 mkshortcut(python,'IPython (PyLab mode)',f,a)
40 'irunner'
61
41 ]
62 f = ip_dir + r'\scipy.lnk'
42 scripts = pjoin(prefix,'scripts')
63 a = ipybase+' -pylab -p scipy'
43 for program in programs:
64 mkshortcut(python,'IPython (scipy profile)',f,a)
44 raw = pjoin(scripts, program)
65
45 bat = raw + '.bat'
46 py = raw + '.py'
47 # Create .py versions of the scripts
48 shutil.copy(raw, py)
49 # Create .bat files for each of the scripts
50 bat_file = file(bat,'w')
51 bat_file.write("@%s %s %%*" % (python, py))
52 bat_file.close()
53
54 # Now move onto setting the Start Menu up
55 ipybase = pjoin(scripts, 'ipython')
56
57 link = pjoin(ip_start_menu, 'IPython.lnk')
58 cmd = '"%s"' % ipybase
59 mkshortcut(python,'IPython',link,cmd)
60
61 link = pjoin(ip_start_menu, 'pysh.lnk')
62 cmd = '"%s" -p sh' % ipybase
63 mkshortcut(python,'IPython (command prompt mode)',link,cmd)
64
65 link = pjoin(ip_start_menu, 'pylab.lnk')
66 cmd = '"%s" -pylab' % ipybase
67 mkshortcut(python,'IPython (PyLab mode)',link,cmd)
68
69 link = pjoin(ip_start_menu, 'scipy.lnk')
70 cmd = '"%s" -pylab -p scipy' % ipybase
71 mkshortcut(python,'IPython (scipy profile)',link,cmd)
72
73 link = pjoin(ip_start_menu, 'IPython test suite.lnk')
74 cmd = '"%s" -vv' % pjoin(scripts, 'iptest')
75 mkshortcut(python,'Run the IPython test suite',link,cmd)
76
77 link = pjoin(ip_start_menu, 'ipcontroller.lnk')
78 cmd = '"%s" -xy' % pjoin(scripts, 'ipcontroller')
79 mkshortcut(python,'IPython controller',link,cmd)
80
81 link = pjoin(ip_start_menu, 'ipengine.lnk')
82 cmd = '"%s"' % pjoin(scripts, 'ipengine')
83 mkshortcut(python,'IPython engine',link,cmd)
84
66 # Create documentation shortcuts ...
85 # Create documentation shortcuts ...
67 t = prefix + r'\share\doc\ipython\manual\ipython.pdf'
86 t = prefix + r'\share\doc\ipython\manual\ipython.pdf'
68 f = ip_dir + r'\Manual in PDF.lnk'
87 f = ip_start_menu + r'\Manual in PDF.lnk'
69 mkshortcut(t,r'IPython Manual - PDF-Format',f)
88 mkshortcut(t,r'IPython Manual - PDF-Format',f)
70
89
71 t = prefix + r'\share\doc\ipython\manual\html\index.html'
90 t = prefix + r'\share\doc\ipython\manual\html\index.html'
72 f = ip_dir + r'\Manual in HTML.lnk'
91 f = ip_start_menu + r'\Manual in HTML.lnk'
73 mkshortcut(t,'IPython Manual - HTML-Format',f)
92 mkshortcut(t,'IPython Manual - HTML-Format',f)
74
93
75 # make ipython.py
76 shutil.copy(prefix + r'\scripts\ipython', prefix + r'\scripts\ipython.py')
77
94
78 def remove():
95 def remove():
79 """Routine to be run by the win32 installer with the -remove switch."""
96 """Routine to be run by the win32 installer with the -remove switch."""
80 pass
97 pass
81
98
82 # main()
99 # main()
83 if len(sys.argv) > 1:
100 if len(sys.argv) > 1:
84 if sys.argv[1] == '-install':
101 if sys.argv[1] == '-install':
85 install()
102 install()
86 elif sys.argv[1] == '-remove':
103 elif sys.argv[1] == '-remove':
87 remove()
104 remove()
88 else:
105 else:
89 print "Script was called with option %s" % sys.argv[1]
106 print "Script was called with option %s" % sys.argv[1]
@@ -1,278 +1,279 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """
3 """
4 This module defines the things that are used in setup.py for building IPython
4 This module defines the things that are used in setup.py for building IPython
5
5
6 This includes:
6 This includes:
7
7
8 * The basic arguments to setup
8 * The basic arguments to setup
9 * Functions for finding things like packages, package data, etc.
9 * Functions for finding things like packages, package data, etc.
10 * A function for checking dependencies.
10 * A function for checking dependencies.
11 """
11 """
12
12
13 __docformat__ = "restructuredtext en"
13 __docformat__ = "restructuredtext en"
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
16 # Copyright (C) 2008 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25
25
26 import os, sys
26 import os, sys
27
27
28 from glob import glob
28 from glob import glob
29
29
30 from setupext import install_data_ext
30 from setupext import install_data_ext
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Useful globals and utility functions
33 # Useful globals and utility functions
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36 # A few handy globals
36 # A few handy globals
37 isfile = os.path.isfile
37 isfile = os.path.isfile
38 pjoin = os.path.join
38 pjoin = os.path.join
39
39
40 def oscmd(s):
40 def oscmd(s):
41 print ">", s
41 print ">", s
42 os.system(s)
42 os.system(s)
43
43
44 # A little utility we'll need below, since glob() does NOT allow you to do
44 # A little utility we'll need below, since glob() does NOT allow you to do
45 # exclusion on multiple endings!
45 # exclusion on multiple endings!
46 def file_doesnt_endwith(test,endings):
46 def file_doesnt_endwith(test,endings):
47 """Return true if test is a file and its name does NOT end with any
47 """Return true if test is a file and its name does NOT end with any
48 of the strings listed in endings."""
48 of the strings listed in endings."""
49 if not isfile(test):
49 if not isfile(test):
50 return False
50 return False
51 for e in endings:
51 for e in endings:
52 if test.endswith(e):
52 if test.endswith(e):
53 return False
53 return False
54 return True
54 return True
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # Basic project information
57 # Basic project information
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 # Release.py contains version, authors, license, url, keywords, etc.
60 # Release.py contains version, authors, license, url, keywords, etc.
61 execfile(pjoin('IPython','Release.py'))
61 execfile(pjoin('IPython','Release.py'))
62
62
63 # Create a dict with the basic information
63 # Create a dict with the basic information
64 # This dict is eventually passed to setup after additional keys are added.
64 # This dict is eventually passed to setup after additional keys are added.
65 setup_args = dict(
65 setup_args = dict(
66 name = name,
66 name = name,
67 version = version,
67 version = version,
68 description = description,
68 description = description,
69 long_description = long_description,
69 long_description = long_description,
70 author = author,
70 author = author,
71 author_email = author_email,
71 author_email = author_email,
72 url = url,
72 url = url,
73 download_url = download_url,
73 download_url = download_url,
74 license = license,
74 license = license,
75 platforms = platforms,
75 platforms = platforms,
76 keywords = keywords,
76 keywords = keywords,
77 cmdclass = {'install_data': install_data_ext},
77 cmdclass = {'install_data': install_data_ext},
78 )
78 )
79
79
80
80
81 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
82 # Find packages
82 # Find packages
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84
84
85 def add_package(packages,pname,config=False,tests=False,scripts=False,
85 def add_package(packages,pname,config=False,tests=False,scripts=False,
86 others=None):
86 others=None):
87 """
87 """
88 Add a package to the list of packages, including certain subpackages.
88 Add a package to the list of packages, including certain subpackages.
89 """
89 """
90 packages.append('.'.join(['IPython',pname]))
90 packages.append('.'.join(['IPython',pname]))
91 if config:
91 if config:
92 packages.append('.'.join(['IPython',pname,'config']))
92 packages.append('.'.join(['IPython',pname,'config']))
93 if tests:
93 if tests:
94 packages.append('.'.join(['IPython',pname,'tests']))
94 packages.append('.'.join(['IPython',pname,'tests']))
95 if scripts:
95 if scripts:
96 packages.append('.'.join(['IPython',pname,'scripts']))
96 packages.append('.'.join(['IPython',pname,'scripts']))
97 if others is not None:
97 if others is not None:
98 for o in others:
98 for o in others:
99 packages.append('.'.join(['IPython',pname,o]))
99 packages.append('.'.join(['IPython',pname,o]))
100
100
101 def find_packages():
101 def find_packages():
102 """
102 """
103 Find all of IPython's packages.
103 Find all of IPython's packages.
104 """
104 """
105 packages = ['IPython']
105 packages = ['IPython']
106 add_package(packages, 'config', tests=True)
106 add_package(packages, 'config', tests=True)
107 add_package(packages , 'Extensions')
107 add_package(packages , 'Extensions')
108 add_package(packages, 'external')
108 add_package(packages, 'external')
109 add_package(packages, 'gui')
109 add_package(packages, 'gui')
110 add_package(packages, 'gui.wx')
110 add_package(packages, 'gui.wx')
111 add_package(packages, 'frontend', tests=True)
111 add_package(packages, 'frontend', tests=True)
112 add_package(packages, 'frontend._process')
112 add_package(packages, 'frontend._process')
113 add_package(packages, 'frontend.wx')
113 add_package(packages, 'frontend.wx')
114 add_package(packages, 'frontend.cocoa', tests=True)
114 add_package(packages, 'frontend.cocoa', tests=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
116 add_package(packages, 'kernel.core', config=True, tests=True)
116 add_package(packages, 'kernel.core', config=True, tests=True)
117 add_package(packages, 'testing', tests=True)
117 add_package(packages, 'testing', tests=True)
118 add_package(packages, 'tests')
118 add_package(packages, 'testing.plugin', tests=False)
119 add_package(packages, 'testing.plugin', tests=False)
119 add_package(packages, 'tools', tests=True)
120 add_package(packages, 'tools', tests=True)
120 add_package(packages, 'UserConfig')
121 add_package(packages, 'UserConfig')
121 return packages
122 return packages
122
123
123 #---------------------------------------------------------------------------
124 #---------------------------------------------------------------------------
124 # Find package data
125 # Find package data
125 #---------------------------------------------------------------------------
126 #---------------------------------------------------------------------------
126
127
127 def find_package_data():
128 def find_package_data():
128 """
129 """
129 Find IPython's package_data.
130 Find IPython's package_data.
130 """
131 """
131 # This is not enough for these things to appear in an sdist.
132 # This is not enough for these things to appear in an sdist.
132 # We need to muck with the MANIFEST to get this to work
133 # We need to muck with the MANIFEST to get this to work
133 package_data = {
134 package_data = {
134 'IPython.UserConfig' : ['*'],
135 'IPython.UserConfig' : ['*'],
135 'IPython.tools.tests' : ['*.txt'],
136 'IPython.tools.tests' : ['*.txt'],
136 'IPython.testing' : ['*.txt']
137 'IPython.testing' : ['*.txt']
137 }
138 }
138 return package_data
139 return package_data
139
140
140
141
141 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
142 # Find data files
143 # Find data files
143 #---------------------------------------------------------------------------
144 #---------------------------------------------------------------------------
144
145
145 def make_dir_struct(tag,base,out_base):
146 def make_dir_struct(tag,base,out_base):
146 """Make the directory structure of all files below a starting dir.
147 """Make the directory structure of all files below a starting dir.
147
148
148 This is just a convenience routine to help build a nested directory
149 This is just a convenience routine to help build a nested directory
149 hierarchy because distutils is too stupid to do this by itself.
150 hierarchy because distutils is too stupid to do this by itself.
150
151
151 XXX - this needs a proper docstring!
152 XXX - this needs a proper docstring!
152 """
153 """
153
154
154 # we'll use these a lot below
155 # we'll use these a lot below
155 lbase = len(base)
156 lbase = len(base)
156 pathsep = os.path.sep
157 pathsep = os.path.sep
157 lpathsep = len(pathsep)
158 lpathsep = len(pathsep)
158
159
159 out = []
160 out = []
160 for (dirpath,dirnames,filenames) in os.walk(base):
161 for (dirpath,dirnames,filenames) in os.walk(base):
161 # we need to strip out the dirpath from the base to map it to the
162 # we need to strip out the dirpath from the base to map it to the
162 # output (installation) path. This requires possibly stripping the
163 # output (installation) path. This requires possibly stripping the
163 # path separator, because otherwise pjoin will not work correctly
164 # path separator, because otherwise pjoin will not work correctly
164 # (pjoin('foo/','/bar') returns '/bar').
165 # (pjoin('foo/','/bar') returns '/bar').
165
166
166 dp_eff = dirpath[lbase:]
167 dp_eff = dirpath[lbase:]
167 if dp_eff.startswith(pathsep):
168 if dp_eff.startswith(pathsep):
168 dp_eff = dp_eff[lpathsep:]
169 dp_eff = dp_eff[lpathsep:]
169 # The output path must be anchored at the out_base marker
170 # The output path must be anchored at the out_base marker
170 out_path = pjoin(out_base,dp_eff)
171 out_path = pjoin(out_base,dp_eff)
171 # Now we can generate the final filenames. Since os.walk only produces
172 # Now we can generate the final filenames. Since os.walk only produces
172 # filenames, we must join back with the dirpath to get full valid file
173 # filenames, we must join back with the dirpath to get full valid file
173 # paths:
174 # paths:
174 pfiles = [pjoin(dirpath,f) for f in filenames]
175 pfiles = [pjoin(dirpath,f) for f in filenames]
175 # Finally, generate the entry we need, which is a triple of (tag,output
176 # Finally, generate the entry we need, which is a triple of (tag,output
176 # path, files) for use as a data_files parameter in install_data.
177 # path, files) for use as a data_files parameter in install_data.
177 out.append((tag,out_path,pfiles))
178 out.append((tag,out_path,pfiles))
178
179
179 return out
180 return out
180
181
181
182
182 def find_data_files():
183 def find_data_files():
183 """
184 """
184 Find IPython's data_files.
185 Find IPython's data_files.
185
186
186 Most of these are docs.
187 Most of these are docs.
187 """
188 """
188
189
189 docdirbase = 'share/doc/ipython'
190 docdirbase = 'share/doc/ipython'
190 manpagebase = 'share/man/man1'
191 manpagebase = 'share/man/man1'
191
192
192 # Simple file lists can be made by hand
193 # Simple file lists can be made by hand
193 manpages = filter(isfile, glob('docs/man/*.1.gz'))
194 manpages = filter(isfile, glob('docs/man/*.1.gz'))
194 igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*'))
195 igridhelpfiles = filter(isfile, glob('IPython/Extensions/igrid_help.*'))
195
196
196 # For nested structures, use the utility above
197 # For nested structures, use the utility above
197 example_files = make_dir_struct('data','docs/examples',
198 example_files = make_dir_struct('data','docs/examples',
198 pjoin(docdirbase,'examples'))
199 pjoin(docdirbase,'examples'))
199 manual_files = make_dir_struct('data','docs/dist',pjoin(docdirbase,'manual'))
200 manual_files = make_dir_struct('data','docs/dist',pjoin(docdirbase,'manual'))
200
201
201 # And assemble the entire output list
202 # And assemble the entire output list
202 data_files = [ ('data',manpagebase, manpages),
203 data_files = [ ('data',manpagebase, manpages),
203 ('data',pjoin(docdirbase,'extensions'),igridhelpfiles),
204 ('data',pjoin(docdirbase,'extensions'),igridhelpfiles),
204 ] + manual_files + example_files
205 ] + manual_files + example_files
205
206
206 ## import pprint # dbg
207 ## import pprint # dbg
207 ## print '*'*80
208 ## print '*'*80
208 ## print 'data files'
209 ## print 'data files'
209 ## pprint.pprint(data_files)
210 ## pprint.pprint(data_files)
210 ## print '*'*80
211 ## print '*'*80
211
212
212 return data_files
213 return data_files
213
214
214 #---------------------------------------------------------------------------
215 #---------------------------------------------------------------------------
215 # Find scripts
216 # Find scripts
216 #---------------------------------------------------------------------------
217 #---------------------------------------------------------------------------
217
218
218 def find_scripts():
219 def find_scripts():
219 """
220 """
220 Find IPython's scripts.
221 Find IPython's scripts.
221 """
222 """
222 scripts = ['IPython/kernel/scripts/ipengine',
223 scripts = ['IPython/kernel/scripts/ipengine',
223 'IPython/kernel/scripts/ipcontroller',
224 'IPython/kernel/scripts/ipcontroller',
224 'IPython/kernel/scripts/ipcluster',
225 'IPython/kernel/scripts/ipcluster',
225 'scripts/ipython',
226 'scripts/ipython',
226 'scripts/ipythonx',
227 'scripts/ipythonx',
227 'scripts/ipython-wx',
228 'scripts/ipython-wx',
228 'scripts/pycolor',
229 'scripts/pycolor',
229 'scripts/irunner',
230 'scripts/irunner',
230 'scripts/iptest',
231 'scripts/iptest',
231 ]
232 ]
232
233
233 # Script to be run by the windows binary installer after the default setup
234 # Script to be run by the windows binary installer after the default setup
234 # routine, to add shortcuts and similar windows-only things. Windows
235 # routine, to add shortcuts and similar windows-only things. Windows
235 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
236 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
236 # doesn't find them.
237 # doesn't find them.
237 if 'bdist_wininst' in sys.argv:
238 if 'bdist_wininst' in sys.argv:
238 if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
239 if len(sys.argv) > 2 and ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
239 print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting."
240 print >> sys.stderr,"ERROR: bdist_wininst must be run alone. Exiting."
240 sys.exit(1)
241 sys.exit(1)
241 scripts.append('scripts/ipython_win_post_install.py')
242 scripts.append('scripts/ipython_win_post_install.py')
242
243
243 return scripts
244 return scripts
244
245
245 #---------------------------------------------------------------------------
246 #---------------------------------------------------------------------------
246 # Verify all dependencies
247 # Verify all dependencies
247 #---------------------------------------------------------------------------
248 #---------------------------------------------------------------------------
248
249
249 def check_for_dependencies():
250 def check_for_dependencies():
250 """Check for IPython's dependencies.
251 """Check for IPython's dependencies.
251
252
252 This function should NOT be called if running under setuptools!
253 This function should NOT be called if running under setuptools!
253 """
254 """
254 from setupext.setupext import (
255 from setupext.setupext import (
255 print_line, print_raw, print_status, print_message,
256 print_line, print_raw, print_status, print_message,
256 check_for_zopeinterface, check_for_twisted,
257 check_for_zopeinterface, check_for_twisted,
257 check_for_foolscap, check_for_pyopenssl,
258 check_for_foolscap, check_for_pyopenssl,
258 check_for_sphinx, check_for_pygments,
259 check_for_sphinx, check_for_pygments,
259 check_for_nose, check_for_pexpect
260 check_for_nose, check_for_pexpect
260 )
261 )
261 print_line()
262 print_line()
262 print_raw("BUILDING IPYTHON")
263 print_raw("BUILDING IPYTHON")
263 print_status('python', sys.version)
264 print_status('python', sys.version)
264 print_status('platform', sys.platform)
265 print_status('platform', sys.platform)
265 if sys.platform == 'win32':
266 if sys.platform == 'win32':
266 print_status('Windows version', sys.getwindowsversion())
267 print_status('Windows version', sys.getwindowsversion())
267
268
268 print_raw("")
269 print_raw("")
269 print_raw("OPTIONAL DEPENDENCIES")
270 print_raw("OPTIONAL DEPENDENCIES")
270
271
271 check_for_zopeinterface()
272 check_for_zopeinterface()
272 check_for_twisted()
273 check_for_twisted()
273 check_for_foolscap()
274 check_for_foolscap()
274 check_for_pyopenssl()
275 check_for_pyopenssl()
275 check_for_sphinx()
276 check_for_sphinx()
276 check_for_pygments()
277 check_for_pygments()
277 check_for_nose()
278 check_for_nose()
278 check_for_pexpect()
279 check_for_pexpect()
@@ -1,20 +1,14 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Wrapper to run setup.py using setuptools."""
2 """Wrapper to run setup.py using setuptools."""
3
3
4 import os
5 import sys
4 import sys
6
5
7 # Add my local path to sys.path
8 home = os.environ['HOME']
9 sys.path.insert(0,'%s/usr/local/lib/python%s/site-packages' %
10 (home,sys.version[:3]))
11
12 # now, import setuptools and call the actual setup
6 # now, import setuptools and call the actual setup
13 import setuptools
7 import setuptools
14 # print sys.argv
8 # print sys.argv
15 #sys.argv=['','bdist_egg']
9 #sys.argv=['','bdist_egg']
16 execfile('setup.py')
10 execfile('setup.py')
17
11
18 # clean up the junk left around by setuptools
12 # clean up the junk left around by setuptools
19 if "develop" not in sys.argv:
13 if "develop" not in sys.argv:
20 os.system('rm -rf ipython.egg-info build')
14 os.system('rm -rf ipython.egg-info build')
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now