##// END OF EJS Templates
Track upstream
Gael Varoquaux -
r1354:678c861f merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,256 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 42;
7 objects = {
8
9 /* Begin PBXContainerItemProxy section */
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
11 isa = PBXContainerItemProxy;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
13 proxyType = 1;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430;
15 remoteInfo = "Cocoa Frontend Plugin";
16 };
17 /* End PBXContainerItemProxy section */
18
19 /* Begin PBXFileReference section */
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
23 /* End PBXFileReference section */
24
25 /* Begin PBXFrameworksBuildPhase section */
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
27 isa = PBXFrameworksBuildPhase;
28 buildActionMask = 2147483647;
29 files = (
30 );
31 runOnlyForDeploymentPostprocessing = 0;
32 };
33 /* End PBXFrameworksBuildPhase section */
34
35 /* Begin PBXGroup section */
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
37 isa = PBXGroup;
38 children = (
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
40 );
41 name = Products;
42 sourceTree = "<group>";
43 };
44 4C96F4FC0E199AB500B03430 = {
45 isa = PBXGroup;
46 children = (
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
50 );
51 sourceTree = "<group>";
52 };
53 /* End PBXGroup section */
54
55 /* Begin PBXLegacyTarget section */
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
57 isa = PBXLegacyTarget;
58 buildArgumentsString = "$(ACTION)";
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
60 buildPhases = (
61 );
62 buildToolPath = /usr/bin/make;
63 buildWorkingDirectory = "";
64 dependencies = (
65 );
66 name = "Cocoa Frontend Plugin";
67 passBuildSettingsInEnvironment = 1;
68 productName = "Cocoa Frontend Plugin";
69 };
70 /* End PBXLegacyTarget section */
71
72 /* Begin PBXNativeTarget section */
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
74 isa = PBXNativeTarget;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
76 buildPhases = (
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
80 );
81 buildRules = (
82 );
83 dependencies = (
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
85 );
86 name = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
89 productType = "com.apple.product-type.bundle";
90 };
91 /* End PBXNativeTarget section */
92
93 /* Begin PBXProject section */
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
95 isa = PBXProject;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
97 compatibilityVersion = "Xcode 2.4";
98 hasScannedForEncodings = 0;
99 mainGroup = 4C96F4FC0E199AB500B03430;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
101 projectDirPath = "";
102 projectRoot = "";
103 targets = (
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
106 );
107 };
108 /* End PBXProject section */
109
110 /* Begin PBXResourcesBuildPhase section */
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
112 isa = PBXResourcesBuildPhase;
113 buildActionMask = 2147483647;
114 files = (
115 );
116 runOnlyForDeploymentPostprocessing = 0;
117 };
118 /* End PBXResourcesBuildPhase section */
119
120 /* Begin PBXSourcesBuildPhase section */
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
122 isa = PBXSourcesBuildPhase;
123 buildActionMask = 2147483647;
124 files = (
125 );
126 runOnlyForDeploymentPostprocessing = 0;
127 };
128 /* End PBXSourcesBuildPhase section */
129
130 /* Begin PBXTargetDependency section */
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
132 isa = PBXTargetDependency;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
135 };
136 /* End PBXTargetDependency section */
137
138 /* Begin XCBuildConfiguration section */
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
140 isa = XCBuildConfiguration;
141 buildSettings = {
142 COPY_PHASE_STRIP = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
145 GCC_MODEL_TUNING = G5;
146 GCC_OPTIMIZATION_LEVEL = 0;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
151 OTHER_LDFLAGS = (
152 "-framework",
153 Foundation,
154 "-framework",
155 AppKit,
156 );
157 PREBINDING = NO;
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
159 WRAPPER_EXTENSION = bundle;
160 ZERO_LINK = YES;
161 };
162 name = Debug;
163 };
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
165 isa = XCBuildConfiguration;
166 buildSettings = {
167 COPY_PHASE_STRIP = YES;
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
170 GCC_MODEL_TUNING = G5;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
175 OTHER_LDFLAGS = (
176 "-framework",
177 Foundation,
178 "-framework",
179 AppKit,
180 );
181 PREBINDING = NO;
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
183 WRAPPER_EXTENSION = bundle;
184 ZERO_LINK = NO;
185 };
186 name = Release;
187 };
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
189 isa = XCBuildConfiguration;
190 buildSettings = {
191 COPY_PHASE_STRIP = NO;
192 };
193 name = Debug;
194 };
195 4C96F5000E199AB500B03430 /* Release */ = {
196 isa = XCBuildConfiguration;
197 buildSettings = {
198 COPY_PHASE_STRIP = YES;
199 };
200 name = Release;
201 };
202 4C96F50D0E199AF100B03430 /* Debug */ = {
203 isa = XCBuildConfiguration;
204 buildSettings = {
205 COPY_PHASE_STRIP = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
207 GCC_OPTIMIZATION_LEVEL = 0;
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
209 };
210 name = Debug;
211 };
212 4C96F50E0E199AF100B03430 /* Release */ = {
213 isa = XCBuildConfiguration;
214 buildSettings = {
215 COPY_PHASE_STRIP = YES;
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
219 ZERO_LINK = NO;
220 };
221 name = Release;
222 };
223 /* End XCBuildConfiguration section */
224
225 /* Begin XCConfigurationList section */
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
227 isa = XCConfigurationList;
228 buildConfigurations = (
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
231 );
232 defaultConfigurationIsVisible = 0;
233 defaultConfigurationName = Release;
234 };
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
236 isa = XCConfigurationList;
237 buildConfigurations = (
238 4C96F4FF0E199AB500B03430 /* Debug */,
239 4C96F5000E199AB500B03430 /* Release */,
240 );
241 defaultConfigurationIsVisible = 0;
242 defaultConfigurationName = Release;
243 };
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
245 isa = XCConfigurationList;
246 buildConfigurations = (
247 4C96F50D0E199AF100B03430 /* Debug */,
248 4C96F50E0E199AF100B03430 /* Release */,
249 );
250 defaultConfigurationIsVisible = 0;
251 defaultConfigurationName = Release;
252 };
253 /* End XCConfigurationList section */
254 };
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
256 }
@@ -0,0 +1,25 b''
1 # encoding: utf-8
2 """
3 Provides a namespace for loading the Cocoa frontend via a Cocoa plugin.
4
5 Author: Barry Wark
6 """
7 __docformat__ = "restructuredtext en"
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 from PyObjCTools import AppHelper
17 from twisted.internet import _threadedselect
18
19 #make sure _threadedselect is installed first
20 reactor = _threadedselect.install()
21
22 # load the Cocoa frontend controller
23 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
24 reactor.interleave(AppHelper.callAfter)
25 assert(reactor.running)
@@ -0,0 +1,6 b''
1 include ./plugins.mk
2
3 all : dist/IPythonCocoaController.plugin
4
5 dist/IPythonCocoaController.plugin : ./IPythonCocoaFrontendLoader.py\
6 ./setup.py No newline at end of file
@@ -0,0 +1,20 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.Placeholder (Do Not Use)</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -0,0 +1,21 b''
1 %.plugin::
2 rm -rf dist/$(notdir $@)
3 rm -rf build dist && \
4 python setup.py py2app -s
5
6 %.py:
7 @echo "test -f $@"
8 @test -f %@
9
10 %.nib:
11 @echo "test -f $@"
12 @test -f %@
13
14 .DEFAULT_GOAL := all
15
16 .PHONY : all clean
17
18 clean :
19 rm -rf build dist
20
21
@@ -0,0 +1,35 b''
1 # encoding: utf-8
2 """
3 setup.py
4
5 Setuptools installer script for generating a Cocoa plugin for the
6 IPython cocoa frontend
7
8 Author: Barry Wark
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 from setuptools import setup
20
21 infoPlist = dict(
22 CFBundleDevelopmentRegion='English',
23 CFBundleIdentifier='org.scipy.ipython.cocoa_frontend',
24 NSPrincipalClass='IPythonCocoaController',
25 )
26
27 setup(
28 plugin=['IPythonCocoaFrontendLoader.py'],
29 setup_requires=['py2app'],
30 options=dict(py2app=dict(
31 plist=infoPlist,
32 site_packages=True,
33 excludes=['IPython','twisted','PyObjCTools']
34 )),
35 ) No newline at end of file
@@ -0,0 +1,20 b''
1 # Set this prefix to where you want to install the plugin
2 PREFIX=~/usr/local
3 PREFIX=~/tmp/local
4
5 plugin: IPython_doctest_plugin.egg-info
6
7 test: plugin dtexample.py
8 nosetests -s --with-ipdoctest --doctest-tests --doctest-extension=txt \
9 dtexample.py test*.txt
10
11 deb: plugin dtexample.py
12 nosetests -vs --with-ipdoctest --doctest-tests --doctest-extension=txt \
13 test_combo.txt
14
15 IPython_doctest_plugin.egg-info: ipdoctest.py setup.py
16 python setup.py install --prefix=$(PREFIX)
17 touch $@
18
19 clean:
20 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
@@ -0,0 +1,39 b''
1 =======================================================
2 Nose plugin with IPython and extension module support
3 =======================================================
4
5 This directory provides the key functionality for test support that IPython
6 needs as a nose plugin, which can be installed for use in projects other than
7 IPython.
8
9 The presence of a Makefile here is mostly for development and debugging
10 purposes as it only provides a few shorthand commands. You can manually
11 install the plugin by using standard Python procedures (``setup.py install``
12 with appropriate arguments).
13
14 To install the plugin using the Makefile, edit its first line to reflect where
15 you'd like the installation. If you want it system-wide, you may want to edit
16 the install line in the plugin target to use sudo and no prefix::
17
18 sudo python setup.py install
19
20 instead of the code using `--prefix` that's in there.
21
22 Once you've set the prefix, simply build/install the plugin with::
23
24 make
25
26 and run the tests with::
27
28 make test
29
30 You should see output similar to::
31
32 maqroll[plugin]> make test
33 nosetests -s --with-ipdoctest --doctest-tests dtexample.py
34 ..
35 ----------------------------------------------------------------------
36 Ran 2 tests in 0.016s
37
38 OK
39
@@ -0,0 +1,72 b''
1 """Simple example using doctests.
2
3 This file just contains doctests both using plain python and IPython prompts.
4 All tests should be loaded by nose.
5 """
6
7 def pyfunc():
8 """Some pure python tests...
9
10 >>> pyfunc()
11 'pyfunc'
12
13 >>> import os
14
15 >>> 2+3
16 5
17
18 >>> for i in range(3):
19 ... print i,
20 ... print i+1,
21 ...
22 0 1 1 2 2 3
23 """
24
25 return 'pyfunc'
26
27 def ipfunc():
28 """Some ipython tests...
29
30 In [1]: import os
31
32 In [2]: cd /
33 /
34
35 In [3]: 2+3
36 Out[3]: 5
37
38 In [26]: for i in range(3):
39 ....: print i,
40 ....: print i+1,
41 ....:
42 0 1 1 2 2 3
43
44
45 Examples that access the operating system work:
46
47 In [1]: !echo hello
48 hello
49
50 In [2]: !echo hello > /tmp/foo
51
52 In [3]: !cat /tmp/foo
53 hello
54
55 In [4]: rm -f /tmp/foo
56
57 It's OK to use '_' for the last result, but do NOT try to use IPython's
58 numbered history of _NN outputs, since those won't exist under the
59 doctest environment:
60
61 In [7]: 3+4
62 Out[7]: 7
63
64 In [8]: _+3
65 Out[8]: 10
66
67 In [9]: ipfunc()
68 Out[9]: 'ipfunc'
69 """
70
71 return 'ipfunc'
72
This diff has been collapsed as it changes many lines, (587 lines changed) Show them Hide them
@@ -0,0 +1,587 b''
1 """Nose Plugin that supports IPython doctests.
2
3 Limitations:
4
5 - When generating examples for use as doctests, make sure that you have
6 pretty-printing OFF. This can be done either by starting ipython with the
7 flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
8 interactively disabling it with %Pprint. This is required so that IPython
9 output matches that of normal Python, which is used by doctest for internal
10 execution.
11
12 - Do not rely on specific prompt numbers for results (such as using
13 '_34==True', for example). For IPython tests run via an external process the
14 prompt numbers may be different, and IPython tests run as normal python code
15 won't even have these special _NN variables set at all.
16
17 - IPython functions that produce output as a side-effect of calling a system
18 process (e.g. 'ls') can be doc-tested, but they must be handled in an
19 external IPython process. Such doctests must be tagged with:
20
21 # ipdoctest: EXTERNAL
22
23 so that the testing machinery handles them differently. Since these are run
24 via pexpect in an external process, they can't deal with exceptions or other
25 fancy featurs of regular doctests. You must limit such tests to simple
26 matching of the output. For this reason, I recommend you limit these kinds
27 of doctests to features that truly require a separate process, and use the
28 normal IPython ones (which have all the features of normal doctests) for
29 everything else. See the examples at the bottom of this file for a
30 comparison of what can be done with both types.
31 """
32
33
34 #-----------------------------------------------------------------------------
35 # Module imports
36
37 # From the standard library
38 import __builtin__
39 import commands
40 import doctest
41 import inspect
42 import logging
43 import os
44 import re
45 import sys
46 import unittest
47
48 from inspect import getmodule
49
50 # Third-party modules
51 import nose.core
52
53 from nose.plugins import doctests, Plugin
54 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
55
56 # Our own imports
57 #from extdoctest import ExtensionDoctest, DocTestFinder
58 #from dttools import DocTestFinder, DocTestCase
59 #-----------------------------------------------------------------------------
60 # Module globals and other constants
61
62 log = logging.getLogger(__name__)
63
64 ###########################################################################
65 # *** HACK ***
66 # We must start our own ipython object and heavily muck with it so that all the
67 # modifications IPython makes to system behavior don't send the doctest
68 # machinery into a fit. This code should be considered a gross hack, but it
69 # gets the job done.
70
71 def start_ipython():
72 """Start a global IPython shell, which we need for IPython-specific syntax.
73 """
74 import IPython
75
76 def xsys(cmd):
77 """Execute a command and print its output.
78
79 This is just a convenience function to replace the IPython system call
80 with one that is more doctest-friendly.
81 """
82 cmd = _ip.IP.var_expand(cmd,depth=1)
83 sys.stdout.write(commands.getoutput(cmd))
84 sys.stdout.flush()
85
86 # Store certain global objects that IPython modifies
87 _displayhook = sys.displayhook
88 _excepthook = sys.excepthook
89 _main = sys.modules.get('__main__')
90
91 # Start IPython instance
92 IPython.Shell.IPShell(['--classic','--noterm_title'])
93
94 # Deactivate the various python system hooks added by ipython for
95 # interactive convenience so we don't confuse the doctest system
96 sys.modules['__main__'] = _main
97 sys.displayhook = _displayhook
98 sys.excepthook = _excepthook
99
100 # So that ipython magics and aliases can be doctested (they work by making
101 # a call into a global _ip object)
102 _ip = IPython.ipapi.get()
103 __builtin__._ip = _ip
104
105 # Modify the IPython system call with one that uses getoutput, so that we
106 # can capture subcommands and print them to Python's stdout, otherwise the
107 # doctest machinery would miss them.
108 _ip.system = xsys
109
110 # The start call MUST be made here. I'm not sure yet why it doesn't work if
111 # it is made later, at plugin initialization time, but in all my tests, that's
112 # the case.
113 start_ipython()
114
115 # *** END HACK ***
116 ###########################################################################
117
118 #-----------------------------------------------------------------------------
119 # Modified version of the one in the stdlib, that fixes a python bug (doctests
120 # not found in extension modules, http://bugs.python.org/issue3158)
121 class DocTestFinder(doctest.DocTestFinder):
122
123 def _from_module(self, module, object):
124 """
125 Return true if the given object is defined in the given
126 module.
127 """
128 if module is None:
129 #print '_fm C1' # dbg
130 return True
131 elif inspect.isfunction(object):
132 #print '_fm C2' # dbg
133 return module.__dict__ is object.func_globals
134 elif inspect.isbuiltin(object):
135 #print '_fm C2-1' # dbg
136 return module.__name__ == object.__module__
137 elif inspect.isclass(object):
138 #print '_fm C3' # dbg
139 return module.__name__ == object.__module__
140 elif inspect.ismethod(object):
141 # This one may be a bug in cython that fails to correctly set the
142 # __module__ attribute of methods, but since the same error is easy
143 # to make by extension code writers, having this safety in place
144 # isn't such a bad idea
145 #print '_fm C3-1' # dbg
146 return module.__name__ == object.im_class.__module__
147 elif inspect.getmodule(object) is not None:
148 #print '_fm C4' # dbg
149 #print 'C4 mod',module,'obj',object # dbg
150 return module is inspect.getmodule(object)
151 elif hasattr(object, '__module__'):
152 #print '_fm C5' # dbg
153 return module.__name__ == object.__module__
154 elif isinstance(object, property):
155 #print '_fm C6' # dbg
156 return True # [XX] no way not be sure.
157 else:
158 raise ValueError("object must be a class or function")
159
160
161
162 def _find(self, tests, obj, name, module, source_lines, globs, seen):
163 """
164 Find tests for the given object and any contained objects, and
165 add them to `tests`.
166 """
167
168 doctest.DocTestFinder._find(self,tests, obj, name, module,
169 source_lines, globs, seen)
170
171 # Below we re-run pieces of the above method with manual modifications,
172 # because the original code is buggy and fails to correctly identify
173 # doctests in extension modules.
174
175 # Local shorthands
176 from inspect import isroutine, isclass, ismodule
177
178 # Look for tests in a module's contained objects.
179 if inspect.ismodule(obj) and self._recurse:
180 for valname, val in obj.__dict__.items():
181 valname1 = '%s.%s' % (name, valname)
182 if ( (isroutine(val) or isclass(val))
183 and self._from_module(module, val) ):
184
185 self._find(tests, val, valname1, module, source_lines,
186 globs, seen)
187
188
189 # Look for tests in a class's contained objects.
190 if inspect.isclass(obj) and self._recurse:
191 #print 'RECURSE into class:',obj # dbg
192 for valname, val in obj.__dict__.items():
193 #valname1 = '%s.%s' % (name, valname) # dbg
194 #print 'N',name,'VN:',valname,'val:',str(val)[:77] # dbg
195 # Special handling for staticmethod/classmethod.
196 if isinstance(val, staticmethod):
197 val = getattr(obj, valname)
198 if isinstance(val, classmethod):
199 val = getattr(obj, valname).im_func
200
201 # Recurse to methods, properties, and nested classes.
202 if ((inspect.isfunction(val) or inspect.isclass(val) or
203 inspect.ismethod(val) or
204 isinstance(val, property)) and
205 self._from_module(module, val)):
206 valname = '%s.%s' % (name, valname)
207 self._find(tests, val, valname, module, source_lines,
208 globs, seen)
209
210
211 class DocTestCase(doctests.DocTestCase):
212 """Proxy for DocTestCase: provides an address() method that
213 returns the correct address for the doctest case. Otherwise
214 acts as a proxy to the test case. To provide hints for address(),
215 an obj may also be passed -- this will be used as the test object
216 for purposes of determining the test address, if it is provided.
217 """
218
219 # doctests loaded via find(obj) omit the module name
220 # so we need to override id, __repr__ and shortDescription
221 # bonus: this will squash a 2.3 vs 2.4 incompatiblity
222 def id(self):
223 name = self._dt_test.name
224 filename = self._dt_test.filename
225 if filename is not None:
226 pk = getpackage(filename)
227 if pk is not None and not name.startswith(pk):
228 name = "%s.%s" % (pk, name)
229 return name
230
231
232 # Classes and functions
233
234 def is_extension_module(filename):
235 """Return whether the given filename is an extension module.
236
237 This simply checks that the extension is either .so or .pyd.
238 """
239 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
240
241
242 # A simple subclassing of the original with a different class name, so we can
243 # distinguish and treat differently IPython examples from pure python ones.
244 class IPExample(doctest.Example): pass
245
246 class IPExternalExample(doctest.Example):
247 """Doctest examples to be run in an external process."""
248
249 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
250 options=None):
251 # Parent constructor
252 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
253
254 # An EXTRA newline is needed to prevent pexpect hangs
255 self.source += '\n'
256
257 class IPDocTestParser(doctest.DocTestParser):
258 """
259 A class used to parse strings containing doctest examples.
260
261 Note: This is a version modified to properly recognize IPython input and
262 convert any IPython examples into valid Python ones.
263 """
264 # This regular expression is used to find doctest examples in a
265 # string. It defines three groups: `source` is the source code
266 # (including leading indentation and prompts); `indent` is the
267 # indentation of the first (PS1) line of the source code; and
268 # `want` is the expected output (including leading indentation).
269
270 # Classic Python prompts or default IPython ones
271 _PS1_PY = r'>>>'
272 _PS2_PY = r'\.\.\.'
273
274 _PS1_IP = r'In\ \[\d+\]:'
275 _PS2_IP = r'\ \ \ \.\.\.+:'
276
277 _RE_TPL = r'''
278 # Source consists of a PS1 line followed by zero or more PS2 lines.
279 (?P<source>
280 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
281 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
282 \n? # a newline
283 # Want consists of any non-blank lines that do not start with PS1.
284 (?P<want> (?:(?![ ]*$) # Not a blank line
285 (?![ ]*%s) # Not a line starting with PS1
286 (?![ ]*%s) # Not a line starting with PS2
287 .*$\n? # But any other line
288 )*)
289 '''
290
291 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
292 re.MULTILINE | re.VERBOSE)
293
294 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
295 re.MULTILINE | re.VERBOSE)
296
297 def ip2py(self,source):
298 """Convert input IPython source into valid Python."""
299 out = []
300 newline = out.append
301 for line in source.splitlines():
302 #newline(_ip.IPipython.prefilter(line,True))
303 newline(_ip.IP.prefilter(line,True))
304 newline('') # ensure a closing newline, needed by doctest
305 return '\n'.join(out)
306
307 def parse(self, string, name='<string>'):
308 """
309 Divide the given string into examples and intervening text,
310 and return them as a list of alternating Examples and strings.
311 Line numbers for the Examples are 0-based. The optional
312 argument `name` is a name identifying this string, and is only
313 used for error messages.
314 """
315
316 #print 'Parse string:\n',string # dbg
317
318 string = string.expandtabs()
319 # If all lines begin with the same indentation, then strip it.
320 min_indent = self._min_indent(string)
321 if min_indent > 0:
322 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
323
324 output = []
325 charno, lineno = 0, 0
326
327 # Whether to convert the input from ipython to python syntax
328 ip2py = False
329 # Find all doctest examples in the string. First, try them as Python
330 # examples, then as IPython ones
331 terms = list(self._EXAMPLE_RE_PY.finditer(string))
332 if terms:
333 # Normal Python example
334 #print '-'*70 # dbg
335 #print 'PyExample, Source:\n',string # dbg
336 #print '-'*70 # dbg
337 Example = doctest.Example
338 else:
339 # It's an ipython example. Note that IPExamples are run
340 # in-process, so their syntax must be turned into valid python.
341 # IPExternalExamples are run out-of-process (via pexpect) so they
342 # don't need any filtering (a real ipython will be executing them).
343 terms = list(self._EXAMPLE_RE_IP.finditer(string))
344 if re.search(r'#\s*ipdoctest:\s*EXTERNAL',string):
345 #print '-'*70 # dbg
346 #print 'IPExternalExample, Source:\n',string # dbg
347 #print '-'*70 # dbg
348 Example = IPExternalExample
349 else:
350 #print '-'*70 # dbg
351 #print 'IPExample, Source:\n',string # dbg
352 #print '-'*70 # dbg
353 Example = IPExample
354 ip2py = True
355
356 for m in terms:
357 # Add the pre-example text to `output`.
358 output.append(string[charno:m.start()])
359 # Update lineno (lines before this example)
360 lineno += string.count('\n', charno, m.start())
361 # Extract info from the regexp match.
362 (source, options, want, exc_msg) = \
363 self._parse_example(m, name, lineno,ip2py)
364 if Example is IPExternalExample:
365 options[doctest.NORMALIZE_WHITESPACE] = True
366 want += '\n'
367 # Create an Example, and add it to the list.
368 if not self._IS_BLANK_OR_COMMENT(source):
369 #print 'Example source:', source # dbg
370 output.append(Example(source, want, exc_msg,
371 lineno=lineno,
372 indent=min_indent+len(m.group('indent')),
373 options=options))
374 # Update lineno (lines inside this example)
375 lineno += string.count('\n', m.start(), m.end())
376 # Update charno.
377 charno = m.end()
378 # Add any remaining post-example text to `output`.
379 output.append(string[charno:])
380
381 return output
382
383 def _parse_example(self, m, name, lineno,ip2py=False):
384 """
385 Given a regular expression match from `_EXAMPLE_RE` (`m`),
386 return a pair `(source, want)`, where `source` is the matched
387 example's source code (with prompts and indentation stripped);
388 and `want` is the example's expected output (with indentation
389 stripped).
390
391 `name` is the string's name, and `lineno` is the line number
392 where the example starts; both are used for error messages.
393
394 Optional:
395 `ip2py`: if true, filter the input via IPython to convert the syntax
396 into valid python.
397 """
398
399 # Get the example's indentation level.
400 indent = len(m.group('indent'))
401
402 # Divide source into lines; check that they're properly
403 # indented; and then strip their indentation & prompts.
404 source_lines = m.group('source').split('\n')
405
406 # We're using variable-length input prompts
407 ps1 = m.group('ps1')
408 ps2 = m.group('ps2')
409 ps1_len = len(ps1)
410
411 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
412 if ps2:
413 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
414
415 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
416
417 if ip2py:
418 # Convert source input from IPython into valid Python syntax
419 source = self.ip2py(source)
420
421 # Divide want into lines; check that it's properly indented; and
422 # then strip the indentation. Spaces before the last newline should
423 # be preserved, so plain rstrip() isn't good enough.
424 want = m.group('want')
425 want_lines = want.split('\n')
426 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
427 del want_lines[-1] # forget final newline & spaces after it
428 self._check_prefix(want_lines, ' '*indent, name,
429 lineno + len(source_lines))
430
431 # Remove ipython output prompt that might be present in the first line
432 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
433
434 want = '\n'.join([wl[indent:] for wl in want_lines])
435
436 # If `want` contains a traceback message, then extract it.
437 m = self._EXCEPTION_RE.match(want)
438 if m:
439 exc_msg = m.group('msg')
440 else:
441 exc_msg = None
442
443 # Extract options from the source.
444 options = self._find_options(source, name, lineno)
445
446 return source, options, want, exc_msg
447
448 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
449 """
450 Given the lines of a source string (including prompts and
451 leading indentation), check to make sure that every prompt is
452 followed by a space character. If any line is not followed by
453 a space character, then raise ValueError.
454
455 Note: IPython-modified version which takes the input prompt length as a
456 parameter, so that prompts of variable length can be dealt with.
457 """
458 space_idx = indent+ps1_len
459 min_len = space_idx+1
460 for i, line in enumerate(lines):
461 if len(line) >= min_len and line[space_idx] != ' ':
462 raise ValueError('line %r of the docstring for %s '
463 'lacks blank after %s: %r' %
464 (lineno+i+1, name,
465 line[indent:space_idx], line))
466
467 SKIP = doctest.register_optionflag('SKIP')
468
469 ###########################################################################
470
471 class DocFileCase(doctest.DocFileCase):
472 """Overrides to provide filename
473 """
474 def address(self):
475 return (self._dt_test.filename, None, None)
476
477
478 class ExtensionDoctest(doctests.Doctest):
479 """Nose Plugin that supports doctests in extension modules.
480 """
481 name = 'extdoctest' # call nosetests with --with-extdoctest
482 enabled = True
483
484 def options(self, parser, env=os.environ):
485 Plugin.options(self, parser, env)
486
487 def configure(self, options, config):
488 Plugin.configure(self, options, config)
489 self.doctest_tests = options.doctest_tests
490 self.extension = tolist(options.doctestExtension)
491 self.finder = DocTestFinder()
492 self.parser = doctest.DocTestParser()
493
494
495 def loadTestsFromExtensionModule(self,filename):
496 bpath,mod = os.path.split(filename)
497 modname = os.path.splitext(mod)[0]
498 try:
499 sys.path.append(bpath)
500 module = __import__(modname)
501 tests = list(self.loadTestsFromModule(module))
502 finally:
503 sys.path.pop()
504 return tests
505
506 def loadTestsFromFile(self, filename):
507 if is_extension_module(filename):
508 for t in self.loadTestsFromExtensionModule(filename):
509 yield t
510 else:
511 ## for t in list(doctests.Doctest.loadTestsFromFile(self,filename)):
512 ## yield t
513 pass
514
515 if self.extension and anyp(filename.endswith, self.extension):
516 #print 'lTF',filename # dbg
517 name = os.path.basename(filename)
518 dh = open(filename)
519 try:
520 doc = dh.read()
521 finally:
522 dh.close()
523 test = self.parser.get_doctest(
524 doc, globs={'__file__': filename}, name=name,
525 filename=filename, lineno=0)
526 if test.examples:
527 #print 'FileCase:',test.examples # dbg
528 yield DocFileCase(test)
529 else:
530 yield False # no tests to load
531
532
533 def wantFile(self,filename):
534 """Return whether the given filename should be scanned for tests.
535
536 Modified version that accepts extension modules as valid containers for
537 doctests.
538 """
539 #print 'Filename:',filename # dbg
540
541 if is_extension_module(filename):
542 return True
543 else:
544 return doctests.Doctest.wantFile(self,filename)
545
546 # NOTE: the method below is a *copy* of the one in the nose doctests
547 # plugin, but we have to replicate it here in order to have it resolve the
548 # DocTestCase (last line) to our local copy, since the nose plugin doesn't
549 # provide a public hook for what TestCase class to use. The alternative
550 # would be to monkeypatch doctest in the stdlib, but that's ugly and
551 # brittle, since a change in plugin load order can break it. So for now,
552 # we just paste this in here, inelegant as this may be.
553
554 def loadTestsFromModule(self, module):
555 #print 'lTM',module # dbg
556
557 if not self.matches(module.__name__):
558 log.debug("Doctest doesn't want module %s", module)
559 return
560 tests = self.finder.find(module)
561 if not tests:
562 return
563 tests.sort()
564 module_file = module.__file__
565 if module_file[-4:] in ('.pyc', '.pyo'):
566 module_file = module_file[:-1]
567 for test in tests:
568 if not test.examples:
569 continue
570 if not test.filename:
571 test.filename = module_file
572 yield DocTestCase(test)
573
574 class IPythonDoctest(ExtensionDoctest):
575 """Nose Plugin that supports doctests in extension modules.
576 """
577 name = 'ipdoctest' # call nosetests with --with-ipdoctest
578 enabled = True
579
580 def configure(self, options, config):
581
582 Plugin.configure(self, options, config)
583 self.doctest_tests = options.doctest_tests
584 self.extension = tolist(options.doctestExtension)
585 self.parser = IPDocTestParser()
586 #self.finder = DocTestFinder(parser=IPDocTestParser())
587 self.finder = DocTestFinder(parser=self.parser)
@@ -0,0 +1,18 b''
1 #!/usr/bin/env python
2 """Nose-based test runner.
3 """
4
5 from nose.core import main
6 from nose.plugins.builtin import plugins
7 from nose.plugins.doctests import Doctest
8
9 import ipdoctest
10 from ipdoctest import IPDocTestRunner
11
12 if __name__ == '__main__':
13 print 'WARNING: this code is incomplete!'
14 print
15
16 pp = [x() for x in plugins] # activate all builtin plugins first
17 main(testRunner=IPDocTestRunner(),
18 plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()])
@@ -0,0 +1,18 b''
1 #!/usr/bin/env python
2 """A Nose plugin to support IPython doctests.
3 """
4
5 from setuptools import setup
6
7 setup(name='IPython doctest plugin',
8 version='0.1',
9 author='The IPython Team',
10 description = 'Nose plugin to load IPython-extended doctests',
11 license = 'LGPL',
12 py_modules = ['ipdoctest'],
13 entry_points = {
14 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest',
15 'extdoctest = ipdoctest:ExtensionDoctest',
16 ],
17 },
18 )
@@ -0,0 +1,36 b''
1 =======================
2 Combo testing example
3 =======================
4
5 This is a simple example that mixes ipython doctests::
6
7 In [1]: import code
8
9 In [2]: 2**12
10 Out[2]: 4096
11
12 with command-line example information that does *not* get executed::
13
14 $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0
15
16 and with literal examples of Python source code::
17
18 controller = dict(host='myhost',
19 engine_port=None, # default is 10105
20 control_port=None,
21 )
22
23 # keys are hostnames, values are the number of engine on that host
24 engines = dict(node1=2,
25 node2=2,
26 node3=2,
27 node3=2,
28 )
29
30 # Force failure to detect that this test is being run.
31 1/0
32
33 These source code examples are executed but no output is compared at all. An
34 error or failure is reported only if an exception is raised.
35
36 NOTE: the execution of pure python blocks is not yet working!
@@ -0,0 +1,24 b''
1 =====================================
2 Tests in example form - pure python
3 =====================================
4
5 This file contains doctest examples embedded as code blocks, using normal
6 Python prompts. See the accompanying file for similar examples using IPython
7 prompts (you can't mix both types within one file). The following will be run
8 as a test::
9
10 >>> 1+1
11 2
12 >>> print "hello"
13 hello
14
15 More than one example works::
16
17 >>> s="Hello World"
18
19 >>> s.upper()
20 'HELLO WORLD'
21
22 but you should note that the *entire* test file is considered to be a single
23 test. Individual code blocks that fail are printed separately as ``example
24 failures``, but the whole file is still counted and reported as one test.
@@ -0,0 +1,30 b''
1 =================================
2 Tests in example form - IPython
3 =================================
4
5 You can write text files with examples that use IPython prompts (as long as you
6 use the nose ipython doctest plugin), but you can not mix and match prompt
7 styles in a single file. That is, you either use all ``>>>`` prompts or all
8 IPython-style prompts. Your test suite *can* have both types, you just need to
9 put each type of example in a separate. Using IPython prompts, you can paste
10 directly from your session::
11
12 In [5]: s="Hello World"
13
14 In [6]: s.upper()
15 Out[6]: 'HELLO WORLD'
16
17 Another example::
18
19 In [8]: 1+3
20 Out[8]: 4
21
22 Just like in IPython docstrings, you can use all IPython syntax and features::
23
24 In [9]: !echo "hello"
25 hello
26
27 In [10]: a='hi'
28
29 In [11]: !echo $a
30 hi
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,23 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 # A super simple example showing how to use all of this in a fully
5 # asynchronous manner. The TaskClient also works in this mode.
6
7 from twisted.internet import reactor, defer
8 from IPython.kernel import asyncclient
9
10 def printer(r):
11 print r
12 return r
13
14 def submit(client):
15 d = client.push(dict(a=5, b='asdf', c=[1,2,3]),targets=0,block=True)
16 d.addCallback(lambda _: client.pull(('a','b','c'),targets=0,block=True))
17 d.addBoth(printer)
18 d.addCallback(lambda _: reactor.stop())
19
20 d = asyncclient.get_multiengine_client()
21 d.addCallback(submit)
22
23 reactor.run() No newline at end of file
@@ -0,0 +1,32 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 # This example shows how the AsynTaskClient can be used
5 # This example is currently broken
6
7 from twisted.internet import reactor, defer
8 from IPython.kernel import asyncclient
9
10 mec = asyncclient.AsyncMultiEngineClient(('localhost', 10105))
11 tc = asyncclient.AsyncTaskClient(('localhost',10113))
12
13 cmd1 = """\
14 a = 5
15 b = 10*d
16 c = a*b*d
17 """
18
19 t1 = asyncclient.Task(cmd1, clear_before=False, clear_after=True, pull=['a','b','c'])
20
21 d = mec.push(dict(d=30))
22
23 def raise_and_print(tr):
24 tr.raiseException()
25 print "a, b: ", tr.ns.a, tr.ns.b
26 return tr
27
28 d.addCallback(lambda _: tc.run(t1))
29 d.addCallback(lambda tid: tc.get_task_result(tid,block=True))
30 d.addCallback(raise_and_print)
31 d.addCallback(lambda _: reactor.stop())
32 reactor.run()
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,95 +1,99 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Release data for the IPython project.
2 """Release data for the IPython project.
3
3
4 $Id: Release.py 3002 2008-02-01 07:17:00Z fperez $"""
4 $Id: Release.py 3002 2008-02-01 07:17:00Z fperez $"""
5
5
6 #*****************************************************************************
6 #*****************************************************************************
7 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
7 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
8 #
8 #
9 # Copyright (c) 2001 Janko Hauser <jhauser@zscout.de> and Nathaniel Gray
9 # Copyright (c) 2001 Janko Hauser <jhauser@zscout.de> and Nathaniel Gray
10 # <n8gray@caltech.edu>
10 # <n8gray@caltech.edu>
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 # Name of the package for release purposes. This is the name which labels
16 # Name of the package for release purposes. This is the name which labels
17 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
17 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
18 name = 'ipython'
18 name = 'ipython'
19
19
20 # For versions with substrings (like 0.6.16.svn), use an extra . to separate
20 # For versions with substrings (like 0.6.16.svn), use an extra . to separate
21 # the new substring. We have to avoid using either dashes or underscores,
21 # the new substring. We have to avoid using either dashes or underscores,
22 # because bdist_rpm does not accept dashes (an RPM) convention, and
22 # because bdist_rpm does not accept dashes (an RPM) convention, and
23 # bdist_deb does not accept underscores (a Debian convention).
23 # bdist_deb does not accept underscores (a Debian convention).
24
24
25 revision = '1016'
25 development = True # change this to False to do a release
26 version_base = '0.9.0'
26 branch = 'ipython'
27 branch = 'ipython'
28 revision = '1016'
27
29
28 if branch == 'ipython':
30 if development:
29 version = '0.9.0.bzr.r' + revision
31 if branch == 'ipython':
32 version = '%s.bzr.r%s' % (version_base, revision)
33 else:
34 version = '%s.bzr.r%s.%s' % (version_base, revision, branch)
30 else:
35 else:
31 version = '0.9.0.bzr.r%s.%s' % (revision,branch)
36 version = version_base
32
37
33 # version = '0.8.4'
34
38
35 description = "Tools for interactive development in Python."
39 description = "Tools for interactive development in Python."
36
40
37 long_description = \
41 long_description = \
38 """
42 """
39 IPython provides a replacement for the interactive Python interpreter with
43 IPython provides a replacement for the interactive Python interpreter with
40 extra functionality.
44 extra functionality.
41
45
42 Main features:
46 Main features:
43
47
44 * Comprehensive object introspection.
48 * Comprehensive object introspection.
45
49
46 * Input history, persistent across sessions.
50 * Input history, persistent across sessions.
47
51
48 * Caching of output results during a session with automatically generated
52 * Caching of output results during a session with automatically generated
49 references.
53 references.
50
54
51 * Readline based name completion.
55 * Readline based name completion.
52
56
53 * Extensible system of 'magic' commands for controlling the environment and
57 * Extensible system of 'magic' commands for controlling the environment and
54 performing many tasks related either to IPython or the operating system.
58 performing many tasks related either to IPython or the operating system.
55
59
56 * Configuration system with easy switching between different setups (simpler
60 * Configuration system with easy switching between different setups (simpler
57 than changing $PYTHONSTARTUP environment variables every time).
61 than changing $PYTHONSTARTUP environment variables every time).
58
62
59 * Session logging and reloading.
63 * Session logging and reloading.
60
64
61 * Extensible syntax processing for special purpose situations.
65 * Extensible syntax processing for special purpose situations.
62
66
63 * Access to the system shell with user-extensible alias system.
67 * Access to the system shell with user-extensible alias system.
64
68
65 * Easily embeddable in other Python programs.
69 * Easily embeddable in other Python programs.
66
70
67 * Integrated access to the pdb debugger and the Python profiler.
71 * Integrated access to the pdb debugger and the Python profiler.
68
72
69 The latest development version is always available at the IPython subversion
73 The latest development version is always available at the IPython subversion
70 repository_.
74 repository_.
71
75
72 .. _repository: http://ipython.scipy.org/svn/ipython/ipython/trunk#egg=ipython-dev
76 .. _repository: http://ipython.scipy.org/svn/ipython/ipython/trunk#egg=ipython-dev
73 """
77 """
74
78
75 license = 'BSD'
79 license = 'BSD'
76
80
77 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
81 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
78 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
82 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
79 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
83 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
80 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
84 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
81 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
85 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
82 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
86 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
83 }
87 }
84
88
85 author = 'The IPython Development Team'
89 author = 'The IPython Development Team'
86
90
87 author_email = 'ipython-dev@scipy.org'
91 author_email = 'ipython-dev@scipy.org'
88
92
89 url = 'http://ipython.scipy.org'
93 url = 'http://ipython.scipy.org'
90
94
91 download_url = 'http://ipython.scipy.org/dist'
95 download_url = 'http://ipython.scipy.org/dist'
92
96
93 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
97 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
94
98
95 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
99 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
@@ -1,203 +1,231 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 r""" mglob - enhanced file list expansion module
3 r""" mglob - enhanced file list expansion module
4
4
5 Use as stand-alone utility (for xargs, `backticks` etc.),
5 Use as stand-alone utility (for xargs, `backticks` etc.),
6 or a globbing library for own python programs. Globbing the sys.argv is something
6 or a globbing library for own python programs. Globbing the sys.argv is something
7 that almost every Windows script has to perform manually, and this module is here
7 that almost every Windows script has to perform manually, and this module is here
8 to help with that task. Also Unix users will benefit from enhanced modes
8 to help with that task. Also Unix users will benefit from enhanced modes
9 such as recursion, exclusion, directory omission...
9 such as recursion, exclusion, directory omission...
10
10
11 Unlike glob.glob, directories are not included in the glob unless specified
11 Unlike glob.glob, directories are not included in the glob unless specified
12 with 'dir:'
12 with 'dir:'
13
13
14 'expand' is the function to use in python programs. Typical use
14 'expand' is the function to use in python programs. Typical use
15 to expand argv (esp. in windows)::
15 to expand argv (esp. in windows)::
16
16
17 try:
17 try:
18 import mglob
18 import mglob
19 files = mglob.expand(sys.argv[1:])
19 files = mglob.expand(sys.argv[1:])
20 except ImportError:
20 except ImportError:
21 print "mglob not found; try 'easy_install mglob' for extra features"
21 print "mglob not found; try 'easy_install mglob' for extra features"
22 files = sys.argv[1:]
22 files = sys.argv[1:]
23
23
24 Note that for unix, shell expands *normal* wildcards (*.cpp, etc.) in argv.
24 Note that for unix, shell expands *normal* wildcards (*.cpp, etc.) in argv.
25 Therefore, you might want to use quotes with normal wildcards to prevent this
25 Therefore, you might want to use quotes with normal wildcards to prevent this
26 expansion, in order for mglob to see the wildcards and get the wanted behaviour.
26 expansion, in order for mglob to see the wildcards and get the wanted behaviour.
27 Not quoting the wildcards is harmless and typically has equivalent results, though.
27 Not quoting the wildcards is harmless and typically has equivalent results, though.
28
28
29 Author: Ville Vainio <vivainio@gmail.com>
29 Author: Ville Vainio <vivainio@gmail.com>
30 License: MIT Open Source license
30 License: MIT Open Source license
31
31
32 """
32 """
33
33
34 #Assigned in variable for "usage" printing convenience"
34 #Assigned in variable for "usage" printing convenience"
35
35
36 globsyntax = """\
36 globsyntax = """\
37 This program allows specifying filenames with "mglob" mechanism.
37 This program allows specifying filenames with "mglob" mechanism.
38 Supported syntax in globs (wilcard matching patterns)::
38 Supported syntax in globs (wilcard matching patterns)::
39
39
40 *.cpp ?ellowo*
40 *.cpp ?ellowo*
41 - obvious. Differs from normal glob in that dirs are not included.
41 - obvious. Differs from normal glob in that dirs are not included.
42 Unix users might want to write this as: "*.cpp" "?ellowo*"
42 Unix users might want to write this as: "*.cpp" "?ellowo*"
43 rec:/usr/share=*.txt,*.doc
43 rec:/usr/share=*.txt,*.doc
44 - get all *.txt and *.doc under /usr/share,
44 - get all *.txt and *.doc under /usr/share,
45 recursively
45 recursively
46 rec:/usr/share
46 rec:/usr/share
47 - All files under /usr/share, recursively
47 - All files under /usr/share, recursively
48 rec:*.py
48 rec:*.py
49 - All .py files under current working dir, recursively
49 - All .py files under current working dir, recursively
50 foo
50 foo
51 - File or dir foo
51 - File or dir foo
52 !*.bak readme*
52 !*.bak readme*
53 - readme*, exclude files ending with .bak
53 - readme*, exclude files ending with .bak
54 !.svn/ !.hg/ !*_Data/ rec:.
54 !.svn/ !.hg/ !*_Data/ rec:.
55 - Skip .svn, .hg, foo_Data dirs (and their subdirs) in recurse.
55 - Skip .svn, .hg, foo_Data dirs (and their subdirs) in recurse.
56 Trailing / is the key, \ does not work!
56 Trailing / is the key, \ does not work! Use !.*/ for all hidden.
57 dir:foo
57 dir:foo
58 - the directory foo if it exists (not files in foo)
58 - the directory foo if it exists (not files in foo)
59 dir:*
59 dir:*
60 - all directories in current folder
60 - all directories in current folder
61 foo.py bar.* !h* rec:*.py
61 foo.py bar.* !h* rec:*.py
62 - Obvious. !h* exclusion only applies for rec:*.py.
62 - Obvious. !h* exclusion only applies for rec:*.py.
63 foo.py is *not* included twice.
63 foo.py is *not* included twice.
64 @filelist.txt
64 @filelist.txt
65 - All files listed in 'filelist.txt' file, on separate lines.
65 - All files listed in 'filelist.txt' file, on separate lines.
66 "cont:class \wak:" rec:*.py
67 - Match files containing regexp. Applies to subsequent files.
68 note quotes because of whitespace.
66 """
69 """
67
70
68
71
69 __version__ = "0.2"
72 __version__ = "0.2"
70
73
71
74
72 import os,glob,fnmatch,sys
75 import os,glob,fnmatch,sys,re
73 from sets import Set as set
76 from sets import Set as set
74
77
75
78
76 def expand(flist,exp_dirs = False):
79 def expand(flist,exp_dirs = False):
77 """ Expand the glob(s) in flist.
80 """ Expand the glob(s) in flist.
78
81
79 flist may be either a whitespace-separated list of globs/files
82 flist may be either a whitespace-separated list of globs/files
80 or an array of globs/files.
83 or an array of globs/files.
81
84
82 if exp_dirs is true, directory names in glob are expanded to the files
85 if exp_dirs is true, directory names in glob are expanded to the files
83 contained in them - otherwise, directory names are returned as is.
86 contained in them - otherwise, directory names are returned as is.
84
87
85 """
88 """
86 if isinstance(flist, basestring):
89 if isinstance(flist, basestring):
87 flist = flist.split()
90 import shlex
91 flist = shlex.split(flist)
88 done_set = set()
92 done_set = set()
89 denied_set = set()
93 denied_set = set()
90
94 cont_set = set()
95 cur_rejected_dirs = set()
96
91 def recfind(p, pats = ["*"]):
97 def recfind(p, pats = ["*"]):
92 denied_dirs = ["*" + d+"*" for d in denied_set if d.endswith("/")]
98 denied_dirs = [os.path.dirname(d) for d in denied_set if d.endswith("/")]
93 #print "de", denied_dirs
94 for (dp,dnames,fnames) in os.walk(p):
99 for (dp,dnames,fnames) in os.walk(p):
95 # see if we should ignore the whole directory
100 # see if we should ignore the whole directory
96 dp_norm = dp.replace("\\","/") + "/"
101 dp_norm = dp.replace("\\","/") + "/"
97 deny = False
102 deny = False
103 # do not traverse under already rejected dirs
104 for d in cur_rejected_dirs:
105 if dp.startswith(d):
106 deny = True
107 break
108 if deny:
109 continue
110
111
98 #print "dp",dp
112 #print "dp",dp
113 bname = os.path.basename(dp)
99 for deny_pat in denied_dirs:
114 for deny_pat in denied_dirs:
100 if fnmatch.fnmatch( dp_norm, deny_pat):
115 if fnmatch.fnmatch( bname, deny_pat):
101 deny = True
116 deny = True
117 cur_rejected_dirs.add(dp)
102 break
118 break
103 if deny:
119 if deny:
104 continue
120 continue
105
121
106
122
107 for f in fnames:
123 for f in fnames:
108 matched = False
124 matched = False
109 for p in pats:
125 for p in pats:
110 if fnmatch.fnmatch(f,p):
126 if fnmatch.fnmatch(f,p):
111 matched = True
127 matched = True
112 break
128 break
113 if matched:
129 if matched:
114 yield os.path.join(dp,f)
130 yield os.path.join(dp,f)
115
131
116 def once_filter(seq):
132 def once_filter(seq):
117 for it in seq:
133 for it in seq:
118 p = os.path.abspath(it)
134 p = os.path.abspath(it)
119 if p in done_set:
135 if p in done_set:
120 continue
136 continue
121 done_set.add(p)
137 done_set.add(p)
122 deny = False
138 deny = False
123 for deny_pat in denied_set:
139 for deny_pat in denied_set:
124 if fnmatch.fnmatch(os.path.basename(p), deny_pat):
140 if fnmatch.fnmatch(os.path.basename(p), deny_pat):
125 deny = True
141 deny = True
126 break
142 break
143 if cont_set:
144 try:
145 cont = open(p).read()
146 except IOError:
147 # deny
148 continue
149 for pat in cont_set:
150 if not re.search(pat,cont, re.IGNORECASE):
151 deny = True
152 break
153
127 if not deny:
154 if not deny:
128 yield it
155 yield it
129 return
156 return
130
157
131 res = []
158 res = []
132
159
133 for ent in flist:
160 for ent in flist:
134 ent = os.path.expanduser(os.path.expandvars(ent))
161 ent = os.path.expanduser(os.path.expandvars(ent))
135 if ent.lower().startswith('rec:'):
162 if ent.lower().startswith('rec:'):
136 fields = ent[4:].split('=')
163 fields = ent[4:].split('=')
137 if len(fields) == 2:
164 if len(fields) == 2:
138 pth, patlist = fields
165 pth, patlist = fields
139 elif len(fields) == 1:
166 elif len(fields) == 1:
140 if os.path.isdir(fields[0]):
167 if os.path.isdir(fields[0]):
141 # single arg is dir
168 # single arg is dir
142 pth, patlist = fields[0], '*'
169 pth, patlist = fields[0], '*'
143 else:
170 else:
144 # single arg is pattern
171 # single arg is pattern
145 pth, patlist = '.', fields[0]
172 pth, patlist = '.', fields[0]
146
173
147 elif len(fields) == 0:
174 elif len(fields) == 0:
148 pth, pathlist = '.','*'
175 pth, pathlist = '.','*'
149
176
150 pats = patlist.split(',')
177 pats = patlist.split(',')
151 res.extend(once_filter(recfind(pth, pats)))
178 res.extend(once_filter(recfind(pth, pats)))
152 # filelist
179 # filelist
153 elif ent.startswith('@') and os.path.isfile(ent[1:]):
180 elif ent.startswith('@') and os.path.isfile(ent[1:]):
154 res.extend(once_filter(open(ent[1:]).read().splitlines()))
181 res.extend(once_filter(open(ent[1:]).read().splitlines()))
155 # exclusion
182 # exclusion
156 elif ent.startswith('!'):
183 elif ent.startswith('!'):
157 denied_set.add(ent[1:])
184 denied_set.add(ent[1:])
158 # glob only dirs
185 # glob only dirs
159 elif ent.lower().startswith('dir:'):
186 elif ent.lower().startswith('dir:'):
160 res.extend(once_filter(filter(os.path.isdir,glob.glob(ent[4:]))))
187 res.extend(once_filter(filter(os.path.isdir,glob.glob(ent[4:]))))
161
188 elif ent.lower().startswith('cont:'):
189 cont_set.add(ent[5:])
162 # get all files in the specified dir
190 # get all files in the specified dir
163 elif os.path.isdir(ent) and exp_dirs:
191 elif os.path.isdir(ent) and exp_dirs:
164 res.extend(once_filter(filter(os.path.isfile,glob.glob(ent + os.sep+"*"))))
192 res.extend(once_filter(filter(os.path.isfile,glob.glob(ent + os.sep+"*"))))
165
193
166 # glob only files
194 # glob only files
167
195
168 elif '*' in ent or '?' in ent:
196 elif '*' in ent or '?' in ent:
169 res.extend(once_filter(filter(os.path.isfile,glob.glob(ent))))
197 res.extend(once_filter(filter(os.path.isfile,glob.glob(ent))))
170
198
171 else:
199 else:
172 res.extend(once_filter([ent]))
200 res.extend(once_filter([ent]))
173 return res
201 return res
174
202
175
203
176 def test():
204 def test():
177 assert (
205 assert (
178 expand("*.py ~/.ipython/*.py rec:/usr/share/doc-base") ==
206 expand("*.py ~/.ipython/*.py rec:/usr/share/doc-base") ==
179 expand( ['*.py', '~/.ipython/*.py', 'rec:/usr/share/doc-base'] )
207 expand( ['*.py', '~/.ipython/*.py', 'rec:/usr/share/doc-base'] )
180 )
208 )
181
209
182 def main():
210 def main():
183 if len(sys.argv) < 2:
211 if len(sys.argv) < 2:
184 print globsyntax
212 print globsyntax
185 return
213 return
186
214
187 print "\n".join(expand(sys.argv[1:])),
215 print "\n".join(expand(sys.argv[1:])),
188
216
189 def mglob_f(self, arg):
217 def mglob_f(self, arg):
190 from IPython.genutils import SList
218 from IPython.genutils import SList
191 if arg.strip():
219 if arg.strip():
192 return SList(expand(arg))
220 return SList(expand(arg))
193 print "Please specify pattern!"
221 print "Please specify pattern!"
194 print globsyntax
222 print globsyntax
195
223
196 def init_ipython(ip):
224 def init_ipython(ip):
197 """ register %mglob for IPython """
225 """ register %mglob for IPython """
198 mglob_f.__doc__ = globsyntax
226 mglob_f.__doc__ = globsyntax
199 ip.expose_magic("mglob",mglob_f)
227 ip.expose_magic("mglob",mglob_f)
200
228
201 # test()
229 # test()
202 if __name__ == "__main__":
230 if __name__ == "__main__":
203 main()
231 main()
@@ -1,425 +1,505 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5 IPython.kernel.engineservice.IEngineBase.
6
6
7 To add an IPython interpreter to a cocoa app, instantiate an
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
9 NSTextView instance in your UI. That's it.
10
10
11 Author: Barry Wark
11 Author: Barry Wark
12 """
12 """
13
13
14 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
17 # Copyright (C) 2008 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import sys
27 import objc
28 import objc
28 import uuid
29 import uuid
29
30
30 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLocalizedString, NSIntersectionRange
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
33
35
34 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
36
38
37 from pprint import saferepr
39 from pprint import saferepr
38
40
39 import IPython
41 import IPython
40 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
41 from IPython.frontend.frontendbase import FrontEndBase
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
42
44
43 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
44 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
45
47
46 #------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
47 # Classes to implement the Cocoa frontend
49 # Classes to implement the Cocoa frontend
48 #------------------------------------------------------------------------------
50 #------------------------------------------------------------------------------
49
51
50 # TODO:
52 # TODO:
51 # 1. use MultiEngineClient and out-of-process engine rather than
53 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
54 # ThreadedEngineService?
53 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
54
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
55
59
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
62 try:
63 p = NSAutoreleasePool.alloc().init()
64 result = self.shell.execute(lines)
65 except Exception,e:
66 # This gives the following:
67 # et=exception class
68 # ev=exception class instance
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
74
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
78
79 # Re-raise
80 raise e
81 finally:
82 p.drain()
83
84 return result
85
86 def execute(self, lines):
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
89
90 msg = {'engineid':self.id,
91 'method':'execute',
92 'args':[lines]}
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
96 return d
56
97
57
98
58 class IPythonCocoaController(NSObject, FrontEndBase):
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
59 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
60 waitingForEngine = objc.ivar().bool()
101 waitingForEngine = objc.ivar().bool()
61 textView = objc.IBOutlet()
102 textView = objc.IBOutlet()
62
103
63 def init(self):
104 def init(self):
64 self = super(IPythonCocoaController, self).init()
105 self = super(IPythonCocoaController, self).init()
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
106 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
66 if(self != None):
108 if(self != None):
67 self._common_init()
109 self._common_init()
68
110
69 return self
111 return self
70
112
71 def _common_init(self):
113 def _common_init(self):
72 """_common_init"""
114 """_common_init"""
73
115
74 self.userNS = NSMutableDictionary.dictionary()
116 self.userNS = NSMutableDictionary.dictionary()
75 self.waitingForEngine = False
117 self.waitingForEngine = False
76
118
77 self.lines = {}
119 self.lines = {}
78 self.tabSpaces = 4
120 self.tabSpaces = 4
79 self.tabUsesSpaces = True
121 self.tabUsesSpaces = True
80 self.currentBlockID = self.next_block_ID()
122 self.currentBlockID = self.next_block_ID()
81 self.blockRanges = {} # blockID=>NSRange
123 self.blockRanges = {} # blockID=>NSRange
82
124
83
125
84 def awakeFromNib(self):
126 def awakeFromNib(self):
85 """awakeFromNib"""
127 """awakeFromNib"""
86
128
87 self._common_init()
129 self._common_init()
88
130
89 # Start the IPython engine
131 # Start the IPython engine
90 self.engine.startService()
132 self.engine.startService()
91 NSLog('IPython engine started')
133 NSLog('IPython engine started')
92
134
93 # Register for app termination
135 # Register for app termination
94 nc = NSNotificationCenter.defaultCenter()
136 nc = NSNotificationCenter.defaultCenter()
95 nc.addObserver_selector_name_object_(
137 nc.addObserver_selector_name_object_(
96 self,
138 self,
97 'appWillTerminate:',
139 'appWillTerminate:',
98 NSApplicationWillTerminateNotification,
140 NSApplicationWillTerminateNotification,
99 None)
141 None)
100
142
101 self.textView.setDelegate_(self)
143 self.textView.setDelegate_(self)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
104 self.textView.enclosingScrollView(),
146 self.textView.enclosingScrollView(),
105 NSVerticalRuler)
147 NSVerticalRuler)
106 self.verticalRulerView = r
148 self.verticalRulerView = r
107 self.verticalRulerView.setClientView_(self.textView)
149 self.verticalRulerView.setClientView_(self.textView)
108 self._start_cli_banner()
150 self._start_cli_banner()
109
151
110
152
111 def appWillTerminate_(self, notification):
153 def appWillTerminate_(self, notification):
112 """appWillTerminate"""
154 """appWillTerminate"""
113
155
114 self.engine.stopService()
156 self.engine.stopService()
115
157
116
158
117 def complete(self, token):
159 def complete(self, token):
118 """Complete token in engine's user_ns
160 """Complete token in engine's user_ns
119
161
120 Parameters
162 Parameters
121 ----------
163 ----------
122 token : string
164 token : string
123
165
124 Result
166 Result
125 ------
167 ------
126 Deferred result of
168 Deferred result of
127 IPython.kernel.engineservice.IEngineBase.complete
169 IPython.kernel.engineservice.IEngineBase.complete
128 """
170 """
129
171
130 return self.engine.complete(token)
172 return self.engine.complete(token)
131
173
132
174
133 def execute(self, block, blockID=None):
175 def execute(self, block, blockID=None):
134 self.waitingForEngine = True
176 self.waitingForEngine = True
135 self.willChangeValueForKey_('commandHistory')
177 self.willChangeValueForKey_('commandHistory')
136 d = super(IPythonCocoaController, self).execute(block, blockID)
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
137 d.addBoth(self._engine_done)
180 d.addBoth(self._engine_done)
138 d.addCallback(self._update_user_ns)
181 d.addCallback(self._update_user_ns)
139
182
140 return d
183 return d
141
184
185
186 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
142
188
189 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
194
195
196 def pull_(self, keys):
197 """Pull keys from python namespace"""
198
199 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
202
203 @objc.signature('v@:@I')
204 def executeFileAtPath_encoding_(self, path, encoding):
205 """Execute file at path in an empty namespace. Update the engine
206 user_ns with the resulting locals."""
207
208 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
209 path,
210 encoding,
211 None)
212 self.engine.execute(lines)
213
214
143 def _engine_done(self, x):
215 def _engine_done(self, x):
144 self.waitingForEngine = False
216 self.waitingForEngine = False
145 self.didChangeValueForKey_('commandHistory')
217 self.didChangeValueForKey_('commandHistory')
146 return x
218 return x
147
219
148 def _update_user_ns(self, result):
220 def _update_user_ns(self, result):
149 """Update self.userNS from self.engine's namespace"""
221 """Update self.userNS from self.engine's namespace"""
150 d = self.engine.keys()
222 d = self.engine.keys()
151 d.addCallback(self._get_engine_namespace_values_for_keys)
223 d.addCallback(self._get_engine_namespace_values_for_keys)
152
224
153 return result
225 return result
154
226
155
227
156 def _get_engine_namespace_values_for_keys(self, keys):
228 def _get_engine_namespace_values_for_keys(self, keys):
157 d = self.engine.pull(keys)
229 d = self.engine.pull(keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
230 d.addCallback(self._store_engine_namespace_values, keys=keys)
159
231
160
232
161 def _store_engine_namespace_values(self, values, keys=[]):
233 def _store_engine_namespace_values(self, values, keys=[]):
162 assert(len(values) == len(keys))
234 assert(len(values) == len(keys))
163 self.willChangeValueForKey_('userNS')
235 self.willChangeValueForKey_('userNS')
164 for (k,v) in zip(keys,values):
236 for (k,v) in zip(keys,values):
165 self.userNS[k] = saferepr(v)
237 self.userNS[k] = saferepr(v)
166 self.didChangeValueForKey_('userNS')
238 self.didChangeValueForKey_('userNS')
167
239
168
240
169 def update_cell_prompt(self, result):
241 def update_cell_prompt(self, result, blockID=None):
170 if(isinstance(result, Failure)):
242 if(isinstance(result, Failure)):
171 blockID = result.blockID
243 self.insert_text(self.input_prompt(),
244 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
245 scrollToVisible=False
246 )
172 else:
247 else:
173 blockID = result['blockID']
248 self.insert_text(self.input_prompt(number=result['number']),
174
175
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
250 scrollToVisible=False
179 )
251 )
180
252
181 return result
253 return result
182
254
183
255
184 def render_result(self, result):
256 def render_result(self, result):
185 blockID = result['blockID']
257 blockID = result['blockID']
186 inputRange = self.blockRanges[blockID]
258 inputRange = self.blockRanges[blockID]
187 del self.blockRanges[blockID]
259 del self.blockRanges[blockID]
188
260
189 #print inputRange,self.current_block_range()
261 #print inputRange,self.current_block_range()
190 self.insert_text('\n' +
262 self.insert_text('\n' +
191 self.output_prompt(result) +
263 self.output_prompt(number=result['number']) +
192 result.get('display',{}).get('pprint','') +
264 result.get('display',{}).get('pprint','') +
193 '\n\n',
265 '\n\n',
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 0))
267 0))
196 return result
268 return result
197
269
198
270
199 def render_error(self, failure):
271 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
272 self.insert_text('\n' +
273 self.output_prompt() +
274 '\n' +
275 failure.getErrorMessage() +
276 '\n\n')
201 self.start_new_block()
277 self.start_new_block()
202 return failure
278 return failure
203
279
204
280
205 def _start_cli_banner(self):
281 def _start_cli_banner(self):
206 """Print banner"""
282 """Print banner"""
207
283
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 IPython.__version__
285 IPython.__version__
210
286
211 self.insert_text(banner + '\n\n')
287 self.insert_text(banner + '\n\n')
212
288
213
289
214 def start_new_block(self):
290 def start_new_block(self):
215 """"""
291 """"""
216
292
217 self.currentBlockID = self.next_block_ID()
293 self.currentBlockID = self.next_block_ID()
218
294
219
295
220
296
221 def next_block_ID(self):
297 def next_block_ID(self):
222
298
223 return uuid.uuid4()
299 return uuid.uuid4()
224
300
225 def current_block_range(self):
301 def current_block_range(self):
226 return self.blockRanges.get(self.currentBlockID,
302 return self.blockRanges.get(self.currentBlockID,
227 NSMakeRange(self.textView.textStorage().length(),
303 NSMakeRange(self.textView.textStorage().length(),
228 0))
304 0))
229
305
230 def current_block(self):
306 def current_block(self):
231 """The current block's text"""
307 """The current block's text"""
232
308
233 return self.text_for_range(self.current_block_range())
309 return self.text_for_range(self.current_block_range())
234
310
235 def text_for_range(self, textRange):
311 def text_for_range(self, textRange):
236 """text_for_range"""
312 """text_for_range"""
237
313
238 ts = self.textView.textStorage()
314 ts = self.textView.textStorage()
239 return ts.string().substringWithRange_(textRange)
315 return ts.string().substringWithRange_(textRange)
240
316
241 def current_line(self):
317 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
318 block = self.text_for_range(self.current_block_range())
243 block = block.split('\n')
319 block = block.split('\n')
244 return block[-1]
320 return block[-1]
245
321
246
322
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 """Insert text into textView at textRange, updating blockRanges
324 """Insert text into textView at textRange, updating blockRanges
249 as necessary
325 as necessary
250 """
326 """
251
327
252 if(textRange == None):
328 if(textRange == None):
253 #range for end of text
329 #range for end of text
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255
331
256 for r in self.blockRanges.itervalues():
332 for r in self.blockRanges.itervalues():
257 intersection = NSIntersectionRange(r,textRange)
333 intersection = NSIntersectionRange(r,textRange)
258 if(intersection.length == 0): #ranges don't intersect
334 if(intersection.length == 0): #ranges don't intersect
259 if r.location >= textRange.location:
335 if r.location >= textRange.location:
260 r.location += len(string)
336 r.location += len(string)
261 else: #ranges intersect
337 else: #ranges intersect
262 if(r.location <= textRange.location):
338 if(r.location <= textRange.location):
263 assert(intersection.length == textRange.length)
339 assert(intersection.length == textRange.length)
264 r.length += textRange.length
340 r.length += textRange.length
265 else:
341 else:
266 r.location += intersection.length
342 r.location += intersection.length
267
343
268 self.textView.replaceCharactersInRange_withString_(
344 self.textView.replaceCharactersInRange_withString_(
269 textRange, string)
345 textRange, string)
270 self.textView.setSelectedRange_(
346 self.textView.setSelectedRange_(
271 NSMakeRange(textRange.location+len(string), 0))
347 NSMakeRange(textRange.location+len(string), 0))
272 if(scrollToVisible):
348 if(scrollToVisible):
273 self.textView.scrollRangeToVisible_(textRange)
349 self.textView.scrollRangeToVisible_(textRange)
274
350
275
351
276
352
277
353
278 def replace_current_block_with_string(self, textView, string):
354 def replace_current_block_with_string(self, textView, string):
279 textView.replaceCharactersInRange_withString_(
355 textView.replaceCharactersInRange_withString_(
280 self.current_block_range(),
356 self.current_block_range(),
281 string)
357 string)
282 self.current_block_range().length = len(string)
358 self.current_block_range().length = len(string)
283 r = NSMakeRange(textView.textStorage().length(), 0)
359 r = NSMakeRange(textView.textStorage().length(), 0)
284 textView.scrollRangeToVisible_(r)
360 textView.scrollRangeToVisible_(r)
285 textView.setSelectedRange_(r)
361 textView.setSelectedRange_(r)
286
362
287
363
288 def current_indent_string(self):
364 def current_indent_string(self):
289 """returns string for indent or None if no indent"""
365 """returns string for indent or None if no indent"""
290
366
291 if(len(self.current_block()) > 0):
367 return self._indent_for_block(self.current_block())
292 lines = self.current_block().split('\n')
368
293 currentIndent = len(lines[-1]) - len(lines[-1])
369
370 def _indent_for_block(self, block):
371 lines = block.split('\n')
372 if(len(lines) > 1):
373 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
294 if(currentIndent == 0):
374 if(currentIndent == 0):
295 currentIndent = self.tabSpaces
375 currentIndent = self.tabSpaces
296
376
297 if(self.tabUsesSpaces):
377 if(self.tabUsesSpaces):
298 result = ' ' * currentIndent
378 result = ' ' * currentIndent
299 else:
379 else:
300 result = '\t' * (currentIndent/self.tabSpaces)
380 result = '\t' * (currentIndent/self.tabSpaces)
301 else:
381 else:
302 result = None
382 result = None
303
383
304 return result
384 return result
305
385
306
386
307 # NSTextView delegate methods...
387 # NSTextView delegate methods...
308 def textView_doCommandBySelector_(self, textView, selector):
388 def textView_doCommandBySelector_(self, textView, selector):
309 assert(textView == self.textView)
389 assert(textView == self.textView)
310 NSLog("textView_doCommandBySelector_: "+selector)
390 NSLog("textView_doCommandBySelector_: "+selector)
311
391
312
392
313 if(selector == 'insertNewline:'):
393 if(selector == 'insertNewline:'):
314 indent = self.current_indent_string()
394 indent = self.current_indent_string()
315 if(indent):
395 if(indent):
316 line = indent + self.current_line()
396 line = indent + self.current_line()
317 else:
397 else:
318 line = self.current_line()
398 line = self.current_line()
319
399
320 if(self.is_complete(self.current_block())):
400 if(self.is_complete(self.current_block())):
321 self.execute(self.current_block(),
401 self.execute(self.current_block(),
322 blockID=self.currentBlockID)
402 blockID=self.currentBlockID)
323 self.start_new_block()
403 self.start_new_block()
324
404
325 return True
405 return True
326
406
327 return False
407 return False
328
408
329 elif(selector == 'moveUp:'):
409 elif(selector == 'moveUp:'):
330 prevBlock = self.get_history_previous(self.current_block())
410 prevBlock = self.get_history_previous(self.current_block())
331 if(prevBlock != None):
411 if(prevBlock != None):
332 self.replace_current_block_with_string(textView, prevBlock)
412 self.replace_current_block_with_string(textView, prevBlock)
333 else:
413 else:
334 NSBeep()
414 NSBeep()
335 return True
415 return True
336
416
337 elif(selector == 'moveDown:'):
417 elif(selector == 'moveDown:'):
338 nextBlock = self.get_history_next()
418 nextBlock = self.get_history_next()
339 if(nextBlock != None):
419 if(nextBlock != None):
340 self.replace_current_block_with_string(textView, nextBlock)
420 self.replace_current_block_with_string(textView, nextBlock)
341 else:
421 else:
342 NSBeep()
422 NSBeep()
343 return True
423 return True
344
424
345 elif(selector == 'moveToBeginningOfParagraph:'):
425 elif(selector == 'moveToBeginningOfParagraph:'):
346 textView.setSelectedRange_(NSMakeRange(
426 textView.setSelectedRange_(NSMakeRange(
347 self.current_block_range().location,
427 self.current_block_range().location,
348 0))
428 0))
349 return True
429 return True
350 elif(selector == 'moveToEndOfParagraph:'):
430 elif(selector == 'moveToEndOfParagraph:'):
351 textView.setSelectedRange_(NSMakeRange(
431 textView.setSelectedRange_(NSMakeRange(
352 self.current_block_range().location + \
432 self.current_block_range().location + \
353 self.current_block_range().length, 0))
433 self.current_block_range().length, 0))
354 return True
434 return True
355 elif(selector == 'deleteToEndOfParagraph:'):
435 elif(selector == 'deleteToEndOfParagraph:'):
356 if(textView.selectedRange().location <= \
436 if(textView.selectedRange().location <= \
357 self.current_block_range().location):
437 self.current_block_range().location):
358 # Intersect the selected range with the current line range
438 # Intersect the selected range with the current line range
359 if(self.current_block_range().length < 0):
439 if(self.current_block_range().length < 0):
360 self.blockRanges[self.currentBlockID].length = 0
440 self.blockRanges[self.currentBlockID].length = 0
361
441
362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
363 self.current_block_range())
443 self.current_block_range())
364
444
365 if(r.length > 0): #no intersection
445 if(r.length > 0): #no intersection
366 textView.setSelectedRange_(r)
446 textView.setSelectedRange_(r)
367
447
368 return False # don't actually handle the delete
448 return False # don't actually handle the delete
369
449
370 elif(selector == 'insertTab:'):
450 elif(selector == 'insertTab:'):
371 if(len(self.current_line().strip()) == 0): #only white space
451 if(len(self.current_line().strip()) == 0): #only white space
372 return False
452 return False
373 else:
453 else:
374 self.textView.complete_(self)
454 self.textView.complete_(self)
375 return True
455 return True
376
456
377 elif(selector == 'deleteBackward:'):
457 elif(selector == 'deleteBackward:'):
378 #if we're at the beginning of the current block, ignore
458 #if we're at the beginning of the current block, ignore
379 if(textView.selectedRange().location == \
459 if(textView.selectedRange().location == \
380 self.current_block_range().location):
460 self.current_block_range().location):
381 return True
461 return True
382 else:
462 else:
383 self.current_block_range().length-=1
463 self.current_block_range().length-=1
384 return False
464 return False
385 return False
465 return False
386
466
387
467
388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 textView, ranges, replacementStrings):
469 textView, ranges, replacementStrings):
390 """
470 """
391 Delegate method for NSTextView.
471 Delegate method for NSTextView.
392
472
393 Refuse change text in ranges not at end, but make those changes at
473 Refuse change text in ranges not at end, but make those changes at
394 end.
474 end.
395 """
475 """
396
476
397 assert(len(ranges) == len(replacementStrings))
477 assert(len(ranges) == len(replacementStrings))
398 allow = True
478 allow = True
399 for r,s in zip(ranges, replacementStrings):
479 for r,s in zip(ranges, replacementStrings):
400 r = r.rangeValue()
480 r = r.rangeValue()
401 if(textView.textStorage().length() > 0 and
481 if(textView.textStorage().length() > 0 and
402 r.location < self.current_block_range().location):
482 r.location < self.current_block_range().location):
403 self.insert_text(s)
483 self.insert_text(s)
404 allow = False
484 allow = False
405
485
406
486
407 self.blockRanges.setdefault(self.currentBlockID,
487 self.blockRanges.setdefault(self.currentBlockID,
408 self.current_block_range()).length +=\
488 self.current_block_range()).length +=\
409 len(s)
489 len(s)
410
490
411 return allow
491 return allow
412
492
413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 textView, words, charRange, index):
494 textView, words, charRange, index):
415 try:
495 try:
416 ts = textView.textStorage()
496 ts = textView.textStorage()
417 token = ts.string().substringWithRange_(charRange)
497 token = ts.string().substringWithRange_(charRange)
418 completions = blockingCallFromThread(self.complete, token)
498 completions = blockingCallFromThread(self.complete, token)
419 except:
499 except:
420 completions = objc.nil
500 completions = objc.nil
421 NSBeep()
501 NSBeep()
422
502
423 return (completions,0)
503 return (completions,0)
424
504
425
505
@@ -1,72 +1,91 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """This file contains unittests for the
2 """This file contains unittests for the
3 IPython.frontend.cocoa.cocoa_frontend module.
3 IPython.frontend.cocoa.cocoa_frontend module.
4 """
4 """
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #---------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2005 The IPython Development Team
8 # Copyright (C) 2005 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 from IPython.kernel.core.interpreter import Interpreter
17 from IPython.kernel.core.interpreter import Interpreter
18 import IPython.kernel.engineservice as es
18 import IPython.kernel.engineservice as es
19 from IPython.testing.util import DeferredTestCase
19 from IPython.testing.util import DeferredTestCase
20 from twisted.internet.defer import succeed
20 from twisted.internet.defer import succeed
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
21 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
22
22
23 from Foundation import NSMakeRect
23 from Foundation import NSMakeRect
24 from AppKit import NSTextView, NSScrollView
24 from AppKit import NSTextView, NSScrollView
25
25
26 class TestIPythonCocoaControler(DeferredTestCase):
26 class TestIPythonCocoaControler(DeferredTestCase):
27 """Tests for IPythonCocoaController"""
27 """Tests for IPythonCocoaController"""
28
28
29 def setUp(self):
29 def setUp(self):
30 self.controller = IPythonCocoaController.alloc().init()
30 self.controller = IPythonCocoaController.alloc().init()
31 self.engine = es.EngineService()
31 self.engine = es.EngineService()
32 self.engine.startService()
32 self.engine.startService()
33
33
34
34
35 def tearDown(self):
35 def tearDown(self):
36 self.controller = None
36 self.controller = None
37 self.engine.stopService()
37 self.engine.stopService()
38
38
39 def testControllerExecutesCode(self):
39 def testControllerExecutesCode(self):
40 code ="""5+5"""
40 code ="""5+5"""
41 expected = Interpreter().execute(code)
41 expected = Interpreter().execute(code)
42 del expected['number']
42 del expected['number']
43 def removeNumberAndID(result):
43 def removeNumberAndID(result):
44 del result['number']
44 del result['number']
45 del result['id']
45 del result['id']
46 return result
46 return result
47 self.assertDeferredEquals(
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
49 expected)
50
50
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
52 code = """userns1=1;userns2=2"""
52 code = """userns1=1;userns2=2"""
53 def testControllerUserNS(result):
53 def testControllerUserNS(result):
54 self.assertEquals(self.controller.userNS['userns1'], 1)
54 self.assertEquals(self.controller.userNS['userns1'], 1)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
55 self.assertEquals(self.controller.userNS['userns2'], 2)
56
56
57 self.controller.execute(code).addCallback(testControllerUserNS)
57 self.controller.execute(code).addCallback(testControllerUserNS)
58
58
59
59
60 def testControllerInstantiatesIEngine(self):
60 def testControllerInstantiatesIEngine(self):
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
61 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
62
62
63 def testControllerCompletesToken(self):
63 def testControllerCompletesToken(self):
64 code = """longNameVariable=10"""
64 code = """longNameVariable=10"""
65 def testCompletes(result):
65 def testCompletes(result):
66 self.assert_("longNameVariable" in result)
66 self.assert_("longNameVariable" in result)
67
67
68 def testCompleteToken(result):
68 def testCompleteToken(result):
69 self.controller.complete("longNa").addCallback(testCompletes)
69 self.controller.complete("longNa").addCallback(testCompletes)
70
70
71 self.controller.execute(code).addCallback(testCompletes)
71 self.controller.execute(code).addCallback(testCompletes)
72
72
73
74 def testCurrentIndent(self):
75 """test that current_indent_string returns current indent or None.
76 Uses _indent_for_block for direct unit testing.
77 """
78
79 self.controller.tabUsesSpaces = True
80 self.assert_(self.controller._indent_for_block("""a=3""") == None)
81 self.assert_(self.controller._indent_for_block("") == None)
82 block = """def test():\n a=3"""
83 self.assert_(self.controller._indent_for_block(block) == \
84 ' ' * self.controller.tabSpaces)
85
86 block = """if(True):\n%sif(False):\n%spass""" % \
87 (' '*self.controller.tabSpaces,
88 2*' '*self.controller.tabSpaces)
89 self.assert_(self.controller._indent_for_block(block) == \
90 2*(' '*self.controller.tabSpaces))
91
@@ -1,352 +1,407 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
3 """
4 frontendbase provides an interface and base class for GUI frontends for
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
5 IPython.kernel/IPython.kernel.core.
6
6
7 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
8
8
9 Author: Barry Wark
9 Author: Barry Wark
10 """
10 """
11 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import string
23 import string
24 import uuid
24 import uuid
25 import _ast
25 import _ast
26
26
27 import zope.interface as zi
27 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
30 #zope.interface is not available
31 Interface = object
32 def Attribute(name, doc): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
28
35
29 from IPython.kernel.core.history import FrontEndHistory
36 from IPython.kernel.core.history import FrontEndHistory
30 from IPython.kernel.core.util import Bunch
37 from IPython.kernel.core.util import Bunch
31 from IPython.kernel.engineservice import IEngineCore
38 from IPython.kernel.engineservice import IEngineCore
32
39
33 from twisted.python.failure import Failure
40 try:
41 from twisted.python.failure import Failure
42 except ImportError:
43 #Twisted not available
44 Failure = Exception
34
45
35 ##############################################################################
46 ##############################################################################
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 # not
48 # not
38
49
39 rc = Bunch()
50 rc = Bunch()
40 rc.prompt_in1 = r'In [$number]: '
51 rc.prompt_in1 = r'In [$number]: '
41 rc.prompt_in2 = r'...'
52 rc.prompt_in2 = r'...'
42 rc.prompt_out = r'Out [$number]: '
53 rc.prompt_out = r'Out [$number]: '
43
54
44 ##############################################################################
55 ##############################################################################
45
56
46 class IFrontEndFactory(zi.Interface):
57 class IFrontEndFactory(Interface):
47 """Factory interface for frontends."""
58 """Factory interface for frontends."""
48
59
49 def __call__(engine=None, history=None):
60 def __call__(engine=None, history=None):
50 """
61 """
51 Parameters:
62 Parameters:
52 interpreter : IPython.kernel.engineservice.IEngineCore
63 interpreter : IPython.kernel.engineservice.IEngineCore
53 """
64 """
54
65
55 pass
66 pass
56
67
57
68
58
69
59 class IFrontEnd(zi.Interface):
70 class IFrontEnd(Interface):
60 """Interface for frontends. All methods return t.i.d.Deferred"""
71 """Interface for frontends. All methods return t.i.d.Deferred"""
61
72
62 zi.Attribute("input_prompt_template", "string.Template instance\
73 Attribute("input_prompt_template", "string.Template instance\
63 substituteable with execute result.")
74 substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
75 Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
76 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
77 Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
78 substituteable with execute result.")
68
79
69 def update_cell_prompt(self, result):
80 def update_cell_prompt(result, blockID=None):
70 """Subclass may override to update the input prompt for a block.
81 """Subclass may override to update the input prompt for a block.
71 Since this method will be called as a
82 Since this method will be called as a
72 twisted.internet.defer.Deferred's callback,
83 twisted.internet.defer.Deferred's callback/errback,
73 implementations should return result when finished.
84 implementations should return result when finished.
74
85
75 NB: result is a failure if the execute returned a failre.
86 Result is a result dict in case of success, and a
76 To get the blockID, you should do something like::
87 twisted.python.util.failure.Failure in case of an error
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
88 """
82
89
83 pass
90 pass
84
91
85 def render_result(self, result):
92
93 def render_result(result):
86 """Render the result of an execute call. Implementors may choose the
94 """Render the result of an execute call. Implementors may choose the
87 method of rendering.
95 method of rendering.
88 For example, a notebook-style frontend might render a Chaco plot
96 For example, a notebook-style frontend might render a Chaco plot
89 inline.
97 inline.
90
98
91 Parameters:
99 Parameters:
92 result : dict (result of IEngineBase.execute )
100 result : dict (result of IEngineBase.execute )
101 blockID = result['blockID']
93
102
94 Result:
103 Result:
95 Output of frontend rendering
104 Output of frontend rendering
96 """
105 """
97
106
98 pass
107 pass
99
108
100 def render_error(self, failure):
109 def render_error(failure):
101 """Subclasses must override to render the failure. Since this method
110 """Subclasses must override to render the failure. Since this method
102 ill be called as a twisted.internet.defer.Deferred's callback,
111 will be called as a twisted.internet.defer.Deferred's callback,
103 implementations should return result when finished.
112 implementations should return result when finished.
113
114 blockID = failure.blockID
104 """
115 """
105
116
106 pass
117 pass
107
118
108
119
109 def input_prompt(result={}):
120 def input_prompt(number=''):
110 """Returns the input prompt by subsituting into
121 """Returns the input prompt by subsituting into
111 self.input_prompt_template
122 self.input_prompt_template
112 """
123 """
113 pass
124 pass
114
125
115 def output_prompt(result):
126 def output_prompt(number=''):
116 """Returns the output prompt by subsituting into
127 """Returns the output prompt by subsituting into
117 self.output_prompt_template
128 self.output_prompt_template
118 """
129 """
119
130
120 pass
131 pass
121
132
122 def continuation_prompt():
133 def continuation_prompt():
123 """Returns the continuation prompt by subsituting into
134 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
135 self.continuation_prompt_template
125 """
136 """
126
137
127 pass
138 pass
128
139
129 def is_complete(block):
140 def is_complete(block):
130 """Returns True if block is complete, False otherwise."""
141 """Returns True if block is complete, False otherwise."""
131
142
132 pass
143 pass
133
144
134 def compile_ast(block):
145 def compile_ast(block):
135 """Compiles block to an _ast.AST"""
146 """Compiles block to an _ast.AST"""
136
147
137 pass
148 pass
138
149
139
150
140 def get_history_previous(currentBlock):
151 def get_history_previous(currentBlock):
141 """Returns the block previous in the history. Saves currentBlock if
152 """Returns the block previous in the history. Saves currentBlock if
142 the history_cursor is currently at the end of the input history"""
153 the history_cursor is currently at the end of the input history"""
143 pass
154 pass
144
155
145 def get_history_next():
156 def get_history_next():
146 """Returns the next block in the history."""
157 """Returns the next block in the history."""
147
158
148 pass
159 pass
149
160
150
161
151 class FrontEndBase(object):
162 class FrontEndBase(object):
152 """
163 """
153 FrontEndBase manages the state tasks for a CLI frontend:
164 FrontEndBase manages the state tasks for a CLI frontend:
154 - Input and output history management
165 - Input and output history management
155 - Input/continuation and output prompt generation
166 - Input/continuation and output prompt generation
156
167
157 Some issues (due to possibly unavailable engine):
168 Some issues (due to possibly unavailable engine):
158 - How do we get the current cell number for the engine?
169 - How do we get the current cell number for the engine?
159 - How do we handle completions?
170 - How do we handle completions?
160 """
171 """
161
172
162 zi.implements(IFrontEnd)
163 zi.classProvides(IFrontEndFactory)
164
165 history_cursor = 0
173 history_cursor = 0
166
174
167 current_indent_level = 0
175 current_indent_level = 0
168
176
169
177
170 input_prompt_template = string.Template(rc.prompt_in1)
178 input_prompt_template = string.Template(rc.prompt_in1)
171 output_prompt_template = string.Template(rc.prompt_out)
179 output_prompt_template = string.Template(rc.prompt_out)
172 continuation_prompt_template = string.Template(rc.prompt_in2)
180 continuation_prompt_template = string.Template(rc.prompt_in2)
173
181
174 def __init__(self, engine=None, history=None):
182 def __init__(self, shell=None, history=None):
175 assert(engine==None or IEngineCore.providedBy(engine))
183 self.shell = shell
176 self.engine = IEngineCore(engine)
177 if history is None:
184 if history is None:
178 self.history = FrontEndHistory(input_cache=[''])
185 self.history = FrontEndHistory(input_cache=[''])
179 else:
186 else:
180 self.history = history
187 self.history = history
181
188
182
189
183 def input_prompt(self, result={}):
190 def input_prompt(self, number=''):
184 """Returns the current input prompt
191 """Returns the current input prompt
185
192
186 It would be great to use ipython1.core.prompts.Prompt1 here
193 It would be great to use ipython1.core.prompts.Prompt1 here
187 """
194 """
188
195 return self.input_prompt_template.safe_substitute({'number':number})
189 result.setdefault('number','')
190
191 return self.input_prompt_template.safe_substitute(result)
192
196
193
197
194 def continuation_prompt(self):
198 def continuation_prompt(self):
195 """Returns the current continuation prompt"""
199 """Returns the current continuation prompt"""
196
200
197 return self.continuation_prompt_template.safe_substitute()
201 return self.continuation_prompt_template.safe_substitute()
198
202
199 def output_prompt(self, result):
203 def output_prompt(self, number=''):
200 """Returns the output prompt for result"""
204 """Returns the output prompt for result"""
201
205
202 return self.output_prompt_template.safe_substitute(result)
206 return self.output_prompt_template.safe_substitute({'number':number})
203
207
204
208
205 def is_complete(self, block):
209 def is_complete(self, block):
206 """Determine if block is complete.
210 """Determine if block is complete.
207
211
208 Parameters
212 Parameters
209 block : string
213 block : string
210
214
211 Result
215 Result
212 True if block can be sent to the engine without compile errors.
216 True if block can be sent to the engine without compile errors.
213 False otherwise.
217 False otherwise.
214 """
218 """
215
219
216 try:
220 try:
217 ast = self.compile_ast(block)
221 ast = self.compile_ast(block)
218 except:
222 except:
219 return False
223 return False
220
224
221 lines = block.split('\n')
225 lines = block.split('\n')
222 return (len(lines)==1 or str(lines[-1])=='')
226 return (len(lines)==1 or str(lines[-1])=='')
223
227
224
228
225 def compile_ast(self, block):
229 def compile_ast(self, block):
226 """Compile block to an AST
230 """Compile block to an AST
227
231
228 Parameters:
232 Parameters:
229 block : str
233 block : str
230
234
231 Result:
235 Result:
232 AST
236 AST
233
237
234 Throws:
238 Throws:
235 Exception if block cannot be compiled
239 Exception if block cannot be compiled
236 """
240 """
237
241
238 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
242 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
239
243
240
244
241 def execute(self, block, blockID=None):
245 def execute(self, block, blockID=None):
242 """Execute the block and return result.
246 """Execute the block and return the result.
243
247
244 Parameters:
248 Parameters:
245 block : {str, AST}
249 block : {str, AST}
246 blockID : any
250 blockID : any
247 Caller may provide an ID to identify this block.
251 Caller may provide an ID to identify this block.
248 result['blockID'] := blockID
252 result['blockID'] := blockID
249
253
250 Result:
254 Result:
251 Deferred result of self.interpreter.execute
255 Deferred result of self.interpreter.execute
252 """
256 """
253
257
254 if(not self.is_complete(block)):
258 if(not self.is_complete(block)):
255 return Failure(Exception("Block is not compilable"))
259 raise Exception("Block is not compilable")
256
260
257 if(blockID == None):
261 if(blockID == None):
258 blockID = uuid.uuid4() #random UUID
262 blockID = uuid.uuid4() #random UUID
259
263
260 d = self.engine.execute(block)
264 try:
261 d.addCallback(self._add_history, block=block)
265 result = self.shell.execute(block)
262 d.addBoth(self._add_block_id, blockID)
266 except Exception,e:
263 d.addBoth(self.update_cell_prompt)
267 e = self._add_block_id_for_failure(e, blockID=blockID)
264 d.addCallbacks(self.render_result, errback=self.render_error)
268 e = self.update_cell_prompt(e, blockID=blockID)
269 e = self.render_error(e)
270 else:
271 result = self._add_block_id_for_result(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
273 result = self.render_result(result)
265
274
266 return d
275 return result
267
276
268
277
269 def _add_block_id(self, result, blockID):
278 def _add_block_id_for_result(self, result, blockID):
270 """Add the blockID to result or failure. Unfortunatley, we have to
279 """Add the blockID to result or failure. Unfortunatley, we have to
271 treat failures differently than result dicts.
280 treat failures differently than result dicts.
272 """
281 """
273
282
274 if(isinstance(result, Failure)):
283 result['blockID'] = blockID
275 result.blockID = blockID
276 else:
277 result['blockID'] = blockID
278
284
279 return result
285 return result
280
286
287 def _add_block_id_for_failure(self, failure, blockID):
288 """_add_block_id_for_failure"""
289
290 failure.blockID = blockID
291 return failure
292
293
281 def _add_history(self, result, block=None):
294 def _add_history(self, result, block=None):
282 """Add block to the history"""
295 """Add block to the history"""
283
296
284 assert(block != None)
297 assert(block != None)
285 self.history.add_items([block])
298 self.history.add_items([block])
286 self.history_cursor += 1
299 self.history_cursor += 1
287
300
288 return result
301 return result
289
302
290
303
291 def get_history_previous(self, currentBlock):
304 def get_history_previous(self, currentBlock):
292 """ Returns previous history string and decrement history cursor.
305 """ Returns previous history string and decrement history cursor.
293 """
306 """
294 command = self.history.get_history_item(self.history_cursor - 1)
307 command = self.history.get_history_item(self.history_cursor - 1)
295
308
296 if command is not None:
309 if command is not None:
297 if(self.history_cursor == len(self.history.input_cache)):
310 if(self.history_cursor == len(self.history.input_cache)):
298 self.history.input_cache[self.history_cursor] = currentBlock
311 self.history.input_cache[self.history_cursor] = currentBlock
299 self.history_cursor -= 1
312 self.history_cursor -= 1
300 return command
313 return command
301
314
302
315
303 def get_history_next(self):
316 def get_history_next(self):
304 """ Returns next history string and increment history cursor.
317 """ Returns next history string and increment history cursor.
305 """
318 """
306 command = self.history.get_history_item(self.history_cursor+1)
319 command = self.history.get_history_item(self.history_cursor+1)
307
320
308 if command is not None:
321 if command is not None:
309 self.history_cursor += 1
322 self.history_cursor += 1
310 return command
323 return command
311
324
312 ###
325 ###
313 # Subclasses probably want to override these methods...
326 # Subclasses probably want to override these methods...
314 ###
327 ###
315
328
316 def update_cell_prompt(self, result):
329 def update_cell_prompt(self, result, blockID=None):
317 """Subclass may override to update the input prompt for a block.
330 """Subclass may override to update the input prompt for a block.
318 Since this method will be called as a
331 Since this method will be called as a
319 twisted.internet.defer.Deferred's callback, implementations should
332 twisted.internet.defer.Deferred's callback, implementations should
320 return result when finished.
333 return result when finished.
321
322 NB: result is a failure if the execute returned a failre.
323 To get the blockID, you should do something like::
324 if(isinstance(result, twisted.python.failure.Failure)):
325 blockID = result.blockID
326 else:
327 blockID = result['blockID']
328
329
330 """
334 """
331
335
332 return result
336 return result
333
337
334
338
335 def render_result(self, result):
339 def render_result(self, result):
336 """Subclasses must override to render result. Since this method will
340 """Subclasses must override to render result. Since this method will
337 be called as a twisted.internet.defer.Deferred's callback,
341 be called as a twisted.internet.defer.Deferred's callback,
338 implementations should return result when finished.
342 implementations should return result when finished.
339 """
343 """
340
344
341 return result
345 return result
342
346
343
347
344 def render_error(self, failure):
348 def render_error(self, failure):
345 """Subclasses must override to render the failure. Since this method
349 """Subclasses must override to render the failure. Since this method
346 will be called as a twisted.internet.defer.Deferred's callback,
350 will be called as a twisted.internet.defer.Deferred's callback,
347 implementations should return result when finished."""
351 implementations should return result when finished.
352 """
348
353
349 return failure
354 return failure
350
355
351
356
352
357
358 class AsyncFrontEndBase(FrontEndBase):
359 """
360 Overrides FrontEndBase to wrap execute in a deferred result.
361 All callbacks are made as callbacks on the deferred result.
362 """
363
364 implements(IFrontEnd)
365 classProvides(IFrontEndFactory)
366
367 def __init__(self, engine=None, history=None):
368 assert(engine==None or IEngineCore.providedBy(engine))
369 self.engine = IEngineCore(engine)
370 if history is None:
371 self.history = FrontEndHistory(input_cache=[''])
372 else:
373 self.history = history
374
375
376 def execute(self, block, blockID=None):
377 """Execute the block and return the deferred result.
378
379 Parameters:
380 block : {str, AST}
381 blockID : any
382 Caller may provide an ID to identify this block.
383 result['blockID'] := blockID
384
385 Result:
386 Deferred result of self.interpreter.execute
387 """
388
389 if(not self.is_complete(block)):
390 return Failure(Exception("Block is not compilable"))
391
392 if(blockID == None):
393 blockID = uuid.uuid4() #random UUID
394
395 d = self.engine.execute(block)
396 d.addCallback(self._add_history, block=block)
397 d.addCallbacks(self._add_block_id_for_result,
398 errback=self._add_block_id_for_failure,
399 callbackArgs=(blockID,),
400 errbackArgs=(blockID,))
401 d.addBoth(self.update_cell_prompt, blockID=blockID)
402 d.addCallbacks(self.render_result,
403 errback=self.render_error)
404
405 return d
406
407
@@ -1,151 +1,151 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 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
20 from IPython.kernel.engineservice import EngineService
20 from IPython.kernel.engineservice import EngineService
21
21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.AsyncFrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 history=history)
27 self.updateCalled = False
27 self.updateCalled = False
28 self.renderResultCalled = False
28 self.renderResultCalled = False
29 self.renderErrorCalled = False
29 self.renderErrorCalled = False
30
30
31 def update_cell_prompt(self, result):
31 def update_cell_prompt(self, result, blockID=None):
32 self.updateCalled = True
32 self.updateCalled = True
33 return result
33 return result
34
34
35 def render_result(self, result):
35 def render_result(self, result):
36 self.renderResultCalled = True
36 self.renderResultCalled = True
37 return result
37 return result
38
38
39
39
40 def render_error(self, failure):
40 def render_error(self, failure):
41 self.renderErrorCalled = True
41 self.renderErrorCalled = True
42 return failure
42 return failure
43
43
44
44
45
45
46
46
47 class TestFrontendBase(unittest.TestCase):
47 class TestAsyncFrontendBase(unittest.TestCase):
48 def setUp(self):
48 def setUp(self):
49 """Setup the EngineService and FrontEndBase"""
49 """Setup the EngineService and FrontEndBase"""
50
50
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
51 self.fb = FrontEndCallbackChecker(engine=EngineService())
52
52
53
53
54 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
55 assert(frontendbase.IFrontEnd.implementedBy(
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
56 frontendbase.AsyncFrontEndBase))
57
57
58
58
59 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
60 """"""
60 """"""
61
61
62 block = """def test(a):"""
62 block = """def test(a):"""
63
63
64 assert(self.fb.is_complete(block) == False)
64 assert(self.fb.is_complete(block) == False)
65
65
66 def test_is_complete_returns_True_for_complete_block(self):
66 def test_is_complete_returns_True_for_complete_block(self):
67 """"""
67 """"""
68
68
69 block = """def test(a): pass"""
69 block = """def test(a): pass"""
70
70
71 assert(self.fb.is_complete(block))
71 assert(self.fb.is_complete(block))
72
72
73 block = """a=3"""
73 block = """a=3"""
74
74
75 assert(self.fb.is_complete(block))
75 assert(self.fb.is_complete(block))
76
76
77
77
78 def test_blockID_added_to_result(self):
78 def test_blockID_added_to_result(self):
79 block = """3+3"""
79 block = """3+3"""
80
80
81 d = self.fb.execute(block, blockID='TEST_ID')
81 d = self.fb.execute(block, blockID='TEST_ID')
82
82
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
83 d.addCallback(self.checkBlockID, expected='TEST_ID')
84
84
85 def test_blockID_added_to_failure(self):
85 def test_blockID_added_to_failure(self):
86 block = "raise Exception()"
86 block = "raise Exception()"
87
87
88 d = self.fb.execute(block,blockID='TEST_ID')
88 d = self.fb.execute(block,blockID='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
89 d.addErrback(self.checkFailureID, expected='TEST_ID')
90
90
91 def checkBlockID(self, result, expected=""):
91 def checkBlockID(self, result, expected=""):
92 assert(result['blockID'] == expected)
92 assert(result['blockID'] == expected)
93
93
94
94
95 def checkFailureID(self, failure, expected=""):
95 def checkFailureID(self, failure, expected=""):
96 assert(failure.blockID == expected)
96 assert(failure.blockID == expected)
97
97
98
98
99 def test_callbacks_added_to_execute(self):
99 def test_callbacks_added_to_execute(self):
100 """test that
100 """test that
101 update_cell_prompt
101 update_cell_prompt
102 render_result
102 render_result
103
103
104 are added to execute request
104 are added to execute request
105 """
105 """
106
106
107 d = self.fb.execute("10+10")
107 d = self.fb.execute("10+10")
108 d.addCallback(self.checkCallbacks)
108 d.addCallback(self.checkCallbacks)
109
109
110
110
111 def checkCallbacks(self, result):
111 def checkCallbacks(self, result):
112 assert(self.fb.updateCalled)
112 assert(self.fb.updateCalled)
113 assert(self.fb.renderResultCalled)
113 assert(self.fb.renderResultCalled)
114
114
115
115
116 def test_error_callback_added_to_execute(self):
116 def test_error_callback_added_to_execute(self):
117 """test that render_error called on execution error"""
117 """test that render_error called on execution error"""
118
118
119 d = self.fb.execute("raise Exception()")
119 d = self.fb.execute("raise Exception()")
120 d.addCallback(self.checkRenderError)
120 d.addCallback(self.checkRenderError)
121
121
122 def checkRenderError(self, result):
122 def checkRenderError(self, result):
123 assert(self.fb.renderErrorCalled)
123 assert(self.fb.renderErrorCalled)
124
124
125 def test_history_returns_expected_block(self):
125 def test_history_returns_expected_block(self):
126 """Make sure history browsing doesn't fail"""
126 """Make sure history browsing doesn't fail"""
127
127
128 blocks = ["a=1","a=2","a=3"]
128 blocks = ["a=1","a=2","a=3"]
129 for b in blocks:
129 for b in blocks:
130 d = self.fb.execute(b)
130 d = self.fb.execute(b)
131
131
132 # d is now the deferred for the last executed block
132 # d is now the deferred for the last executed block
133 d.addCallback(self.historyTests, blocks)
133 d.addCallback(self.historyTests, blocks)
134
134
135
135
136 def historyTests(self, result, blocks):
136 def historyTests(self, result, blocks):
137 """historyTests"""
137 """historyTests"""
138
138
139 assert(len(blocks) >= 3)
139 assert(len(blocks) >= 3)
140 assert(self.fb.get_history_previous("") == blocks[-2])
140 assert(self.fb.get_history_previous("") == blocks[-2])
141 assert(self.fb.get_history_previous("") == blocks[-3])
141 assert(self.fb.get_history_previous("") == blocks[-3])
142 assert(self.fb.get_history_next() == blocks[-2])
142 assert(self.fb.get_history_next() == blocks[-2])
143
143
144
144
145 def test_history_returns_none_at_startup(self):
145 def test_history_returns_none_at_startup(self):
146 """test_history_returns_none_at_startup"""
146 """test_history_returns_none_at_startup"""
147
147
148 assert(self.fb.get_history_previous("")==None)
148 assert(self.fb.get_history_previous("")==None)
149 assert(self.fb.get_history_next()==None)
149 assert(self.fb.get_history_next()==None)
150
150
151
151
@@ -1,2097 +1,2124 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 And used as:
481 And used as:
482 >>> sysexec.xsys('pwd')
482 >>> sysexec.xsys('pwd')
483 >>> dirlist = sysexec.bq('ls -l')
483 >>> dirlist = sysexec.bq('ls -l')
484 """
484 """
485
485
486 def __init__(self,verbose=0,debug=0,header='',split=0):
486 def __init__(self,verbose=0,debug=0,header='',split=0):
487 """Specify the instance's values for verbose, debug and header."""
487 """Specify the instance's values for verbose, debug and header."""
488 setattr_list(self,'verbose debug header split')
488 setattr_list(self,'verbose debug header split')
489
489
490 def system(self,cmd):
490 def system(self,cmd):
491 """Stateful interface to system(), with the same keyword parameters."""
491 """Stateful interface to system(), with the same keyword parameters."""
492
492
493 system(cmd,self.verbose,self.debug,self.header)
493 system(cmd,self.verbose,self.debug,self.header)
494
494
495 def shell(self,cmd):
495 def shell(self,cmd):
496 """Stateful interface to shell(), with the same keyword parameters."""
496 """Stateful interface to shell(), with the same keyword parameters."""
497
497
498 shell(cmd,self.verbose,self.debug,self.header)
498 shell(cmd,self.verbose,self.debug,self.header)
499
499
500 xsys = system # alias
500 xsys = system # alias
501
501
502 def getoutput(self,cmd):
502 def getoutput(self,cmd):
503 """Stateful interface to getoutput()."""
503 """Stateful interface to getoutput()."""
504
504
505 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
505 return getoutput(cmd,self.verbose,self.debug,self.header,self.split)
506
506
507 def getoutputerror(self,cmd):
507 def getoutputerror(self,cmd):
508 """Stateful interface to getoutputerror()."""
508 """Stateful interface to getoutputerror()."""
509
509
510 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
510 return getoutputerror(cmd,self.verbose,self.debug,self.header,self.split)
511
511
512 bq = getoutput # alias
512 bq = getoutput # alias
513
513
514 #-----------------------------------------------------------------------------
514 #-----------------------------------------------------------------------------
515 def mutex_opts(dict,ex_op):
515 def mutex_opts(dict,ex_op):
516 """Check for presence of mutually exclusive keys in a dict.
516 """Check for presence of mutually exclusive keys in a dict.
517
517
518 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
518 Call: mutex_opts(dict,[[op1a,op1b],[op2a,op2b]...]"""
519 for op1,op2 in ex_op:
519 for op1,op2 in ex_op:
520 if op1 in dict and op2 in dict:
520 if op1 in dict and op2 in dict:
521 raise ValueError,'\n*** ERROR in Arguments *** '\
521 raise ValueError,'\n*** ERROR in Arguments *** '\
522 'Options '+op1+' and '+op2+' are mutually exclusive.'
522 'Options '+op1+' and '+op2+' are mutually exclusive.'
523
523
524 #-----------------------------------------------------------------------------
524 #-----------------------------------------------------------------------------
525 def get_py_filename(name):
525 def get_py_filename(name):
526 """Return a valid python filename in the current directory.
526 """Return a valid python filename in the current directory.
527
527
528 If the given name is not a file, it adds '.py' and searches again.
528 If the given name is not a file, it adds '.py' and searches again.
529 Raises IOError with an informative message if the file isn't found."""
529 Raises IOError with an informative message if the file isn't found."""
530
530
531 name = os.path.expanduser(name)
531 name = os.path.expanduser(name)
532 if not os.path.isfile(name) and not name.endswith('.py'):
532 if not os.path.isfile(name) and not name.endswith('.py'):
533 name += '.py'
533 name += '.py'
534 if os.path.isfile(name):
534 if os.path.isfile(name):
535 return name
535 return name
536 else:
536 else:
537 raise IOError,'File `%s` not found.' % name
537 raise IOError,'File `%s` not found.' % name
538
538
539 #-----------------------------------------------------------------------------
539 #-----------------------------------------------------------------------------
540 def filefind(fname,alt_dirs = None):
540 def filefind(fname,alt_dirs = None):
541 """Return the given filename either in the current directory, if it
541 """Return the given filename either in the current directory, if it
542 exists, or in a specified list of directories.
542 exists, or in a specified list of directories.
543
543
544 ~ expansion is done on all file and directory names.
544 ~ expansion is done on all file and directory names.
545
545
546 Upon an unsuccessful search, raise an IOError exception."""
546 Upon an unsuccessful search, raise an IOError exception."""
547
547
548 if alt_dirs is None:
548 if alt_dirs is None:
549 try:
549 try:
550 alt_dirs = get_home_dir()
550 alt_dirs = get_home_dir()
551 except HomeDirError:
551 except HomeDirError:
552 alt_dirs = os.getcwd()
552 alt_dirs = os.getcwd()
553 search = [fname] + list_strings(alt_dirs)
553 search = [fname] + list_strings(alt_dirs)
554 search = map(os.path.expanduser,search)
554 search = map(os.path.expanduser,search)
555 #print 'search list for',fname,'list:',search # dbg
555 #print 'search list for',fname,'list:',search # dbg
556 fname = search[0]
556 fname = search[0]
557 if os.path.isfile(fname):
557 if os.path.isfile(fname):
558 return fname
558 return fname
559 for direc in search[1:]:
559 for direc in search[1:]:
560 testname = os.path.join(direc,fname)
560 testname = os.path.join(direc,fname)
561 #print 'testname',testname # dbg
561 #print 'testname',testname # dbg
562 if os.path.isfile(testname):
562 if os.path.isfile(testname):
563 return testname
563 return testname
564 raise IOError,'File' + `fname` + \
564 raise IOError,'File' + `fname` + \
565 ' not found in current or supplied directories:' + `alt_dirs`
565 ' not found in current or supplied directories:' + `alt_dirs`
566
566
567 #----------------------------------------------------------------------------
567 #----------------------------------------------------------------------------
568 def file_read(filename):
568 def file_read(filename):
569 """Read a file and close it. Returns the file source."""
569 """Read a file and close it. Returns the file source."""
570 fobj = open(filename,'r');
570 fobj = open(filename,'r');
571 source = fobj.read();
571 source = fobj.read();
572 fobj.close()
572 fobj.close()
573 return source
573 return source
574
574
575 def file_readlines(filename):
575 def file_readlines(filename):
576 """Read a file and close it. Returns the file source using readlines()."""
576 """Read a file and close it. Returns the file source using readlines()."""
577 fobj = open(filename,'r');
577 fobj = open(filename,'r');
578 lines = fobj.readlines();
578 lines = fobj.readlines();
579 fobj.close()
579 fobj.close()
580 return lines
580 return lines
581
581
582 #----------------------------------------------------------------------------
582 #----------------------------------------------------------------------------
583 def target_outdated(target,deps):
583 def target_outdated(target,deps):
584 """Determine whether a target is out of date.
584 """Determine whether a target is out of date.
585
585
586 target_outdated(target,deps) -> 1/0
586 target_outdated(target,deps) -> 1/0
587
587
588 deps: list of filenames which MUST exist.
588 deps: list of filenames which MUST exist.
589 target: single filename which may or may not exist.
589 target: single filename which may or may not exist.
590
590
591 If target doesn't exist or is older than any file listed in deps, return
591 If target doesn't exist or is older than any file listed in deps, return
592 true, otherwise return false.
592 true, otherwise return false.
593 """
593 """
594 try:
594 try:
595 target_time = os.path.getmtime(target)
595 target_time = os.path.getmtime(target)
596 except os.error:
596 except os.error:
597 return 1
597 return 1
598 for dep in deps:
598 for dep in deps:
599 dep_time = os.path.getmtime(dep)
599 dep_time = os.path.getmtime(dep)
600 if dep_time > target_time:
600 if dep_time > target_time:
601 #print "For target",target,"Dep failed:",dep # dbg
601 #print "For target",target,"Dep failed:",dep # dbg
602 #print "times (dep,tar):",dep_time,target_time # dbg
602 #print "times (dep,tar):",dep_time,target_time # dbg
603 return 1
603 return 1
604 return 0
604 return 0
605
605
606 #-----------------------------------------------------------------------------
606 #-----------------------------------------------------------------------------
607 def target_update(target,deps,cmd):
607 def target_update(target,deps,cmd):
608 """Update a target with a given command given a list of dependencies.
608 """Update a target with a given command given a list of dependencies.
609
609
610 target_update(target,deps,cmd) -> runs cmd if target is outdated.
610 target_update(target,deps,cmd) -> runs cmd if target is outdated.
611
611
612 This is just a wrapper around target_outdated() which calls the given
612 This is just a wrapper around target_outdated() which calls the given
613 command if target is outdated."""
613 command if target is outdated."""
614
614
615 if target_outdated(target,deps):
615 if target_outdated(target,deps):
616 xsys(cmd)
616 xsys(cmd)
617
617
618 #----------------------------------------------------------------------------
618 #----------------------------------------------------------------------------
619 def unquote_ends(istr):
619 def unquote_ends(istr):
620 """Remove a single pair of quotes from the endpoints of a string."""
620 """Remove a single pair of quotes from the endpoints of a string."""
621
621
622 if not istr:
622 if not istr:
623 return istr
623 return istr
624 if (istr[0]=="'" and istr[-1]=="'") or \
624 if (istr[0]=="'" and istr[-1]=="'") or \
625 (istr[0]=='"' and istr[-1]=='"'):
625 (istr[0]=='"' and istr[-1]=='"'):
626 return istr[1:-1]
626 return istr[1:-1]
627 else:
627 else:
628 return istr
628 return istr
629
629
630 #----------------------------------------------------------------------------
630 #----------------------------------------------------------------------------
631 def process_cmdline(argv,names=[],defaults={},usage=''):
631 def process_cmdline(argv,names=[],defaults={},usage=''):
632 """ Process command-line options and arguments.
632 """ Process command-line options and arguments.
633
633
634 Arguments:
634 Arguments:
635
635
636 - argv: list of arguments, typically sys.argv.
636 - argv: list of arguments, typically sys.argv.
637
637
638 - names: list of option names. See DPyGetOpt docs for details on options
638 - names: list of option names. See DPyGetOpt docs for details on options
639 syntax.
639 syntax.
640
640
641 - defaults: dict of default values.
641 - defaults: dict of default values.
642
642
643 - usage: optional usage notice to print if a wrong argument is passed.
643 - usage: optional usage notice to print if a wrong argument is passed.
644
644
645 Return a dict of options and a list of free arguments."""
645 Return a dict of options and a list of free arguments."""
646
646
647 getopt = DPyGetOpt.DPyGetOpt()
647 getopt = DPyGetOpt.DPyGetOpt()
648 getopt.setIgnoreCase(0)
648 getopt.setIgnoreCase(0)
649 getopt.parseConfiguration(names)
649 getopt.parseConfiguration(names)
650
650
651 try:
651 try:
652 getopt.processArguments(argv)
652 getopt.processArguments(argv)
653 except DPyGetOpt.ArgumentError, exc:
653 except DPyGetOpt.ArgumentError, exc:
654 print usage
654 print usage
655 warn('"%s"' % exc,level=4)
655 warn('"%s"' % exc,level=4)
656
656
657 defaults.update(getopt.optionValues)
657 defaults.update(getopt.optionValues)
658 args = getopt.freeValues
658 args = getopt.freeValues
659
659
660 return defaults,args
660 return defaults,args
661
661
662 #----------------------------------------------------------------------------
662 #----------------------------------------------------------------------------
663 def optstr2types(ostr):
663 def optstr2types(ostr):
664 """Convert a string of option names to a dict of type mappings.
664 """Convert a string of option names to a dict of type mappings.
665
665
666 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
666 optstr2types(str) -> {None:'string_opts',int:'int_opts',float:'float_opts'}
667
667
668 This is used to get the types of all the options in a string formatted
668 This is used to get the types of all the options in a string formatted
669 with the conventions of DPyGetOpt. The 'type' None is used for options
669 with the conventions of DPyGetOpt. The 'type' None is used for options
670 which are strings (they need no further conversion). This function's main
670 which are strings (they need no further conversion). This function's main
671 use is to get a typemap for use with read_dict().
671 use is to get a typemap for use with read_dict().
672 """
672 """
673
673
674 typeconv = {None:'',int:'',float:''}
674 typeconv = {None:'',int:'',float:''}
675 typemap = {'s':None,'i':int,'f':float}
675 typemap = {'s':None,'i':int,'f':float}
676 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
676 opt_re = re.compile(r'([\w]*)([^:=]*:?=?)([sif]?)')
677
677
678 for w in ostr.split():
678 for w in ostr.split():
679 oname,alias,otype = opt_re.match(w).groups()
679 oname,alias,otype = opt_re.match(w).groups()
680 if otype == '' or alias == '!': # simple switches are integers too
680 if otype == '' or alias == '!': # simple switches are integers too
681 otype = 'i'
681 otype = 'i'
682 typeconv[typemap[otype]] += oname + ' '
682 typeconv[typemap[otype]] += oname + ' '
683 return typeconv
683 return typeconv
684
684
685 #----------------------------------------------------------------------------
685 #----------------------------------------------------------------------------
686 def read_dict(filename,type_conv=None,**opt):
686 def read_dict(filename,type_conv=None,**opt):
687
687
688 """Read a dictionary of key=value pairs from an input file, optionally
688 """Read a dictionary of key=value pairs from an input file, optionally
689 performing conversions on the resulting values.
689 performing conversions on the resulting values.
690
690
691 read_dict(filename,type_conv,**opt) -> dict
691 read_dict(filename,type_conv,**opt) -> dict
692
692
693 Only one value per line is accepted, the format should be
693 Only one value per line is accepted, the format should be
694 # optional comments are ignored
694 # optional comments are ignored
695 key value\n
695 key value\n
696
696
697 Args:
697 Args:
698
698
699 - type_conv: A dictionary specifying which keys need to be converted to
699 - type_conv: A dictionary specifying which keys need to be converted to
700 which types. By default all keys are read as strings. This dictionary
700 which types. By default all keys are read as strings. This dictionary
701 should have as its keys valid conversion functions for strings
701 should have as its keys valid conversion functions for strings
702 (int,long,float,complex, or your own). The value for each key
702 (int,long,float,complex, or your own). The value for each key
703 (converter) should be a whitespace separated string containing the names
703 (converter) should be a whitespace separated string containing the names
704 of all the entries in the file to be converted using that function. For
704 of all the entries in the file to be converted using that function. For
705 keys to be left alone, use None as the conversion function (only needed
705 keys to be left alone, use None as the conversion function (only needed
706 with purge=1, see below).
706 with purge=1, see below).
707
707
708 - opt: dictionary with extra options as below (default in parens)
708 - opt: dictionary with extra options as below (default in parens)
709
709
710 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
710 purge(0): if set to 1, all keys *not* listed in type_conv are purged out
711 of the dictionary to be returned. If purge is going to be used, the
711 of the dictionary to be returned. If purge is going to be used, the
712 set of keys to be left as strings also has to be explicitly specified
712 set of keys to be left as strings also has to be explicitly specified
713 using the (non-existent) conversion function None.
713 using the (non-existent) conversion function None.
714
714
715 fs(None): field separator. This is the key/value separator to be used
715 fs(None): field separator. This is the key/value separator to be used
716 when parsing the file. The None default means any whitespace [behavior
716 when parsing the file. The None default means any whitespace [behavior
717 of string.split()].
717 of string.split()].
718
718
719 strip(0): if 1, strip string values of leading/trailinig whitespace.
719 strip(0): if 1, strip string values of leading/trailinig whitespace.
720
720
721 warn(1): warning level if requested keys are not found in file.
721 warn(1): warning level if requested keys are not found in file.
722 - 0: silently ignore.
722 - 0: silently ignore.
723 - 1: inform but proceed.
723 - 1: inform but proceed.
724 - 2: raise KeyError exception.
724 - 2: raise KeyError exception.
725
725
726 no_empty(0): if 1, remove keys with whitespace strings as a value.
726 no_empty(0): if 1, remove keys with whitespace strings as a value.
727
727
728 unique([]): list of keys (or space separated string) which can't be
728 unique([]): list of keys (or space separated string) which can't be
729 repeated. If one such key is found in the file, each new instance
729 repeated. If one such key is found in the file, each new instance
730 overwrites the previous one. For keys not listed here, the behavior is
730 overwrites the previous one. For keys not listed here, the behavior is
731 to make a list of all appearances.
731 to make a list of all appearances.
732
732
733 Example:
733 Example:
734 If the input file test.ini has:
734 If the input file test.ini has:
735 i 3
735 i 3
736 x 4.5
736 x 4.5
737 y 5.5
737 y 5.5
738 s hi ho
738 s hi ho
739 Then:
739 Then:
740
740
741 >>> type_conv={int:'i',float:'x',None:'s'}
741 >>> type_conv={int:'i',float:'x',None:'s'}
742 >>> read_dict('test.ini')
742 >>> read_dict('test.ini')
743 {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'}
743 {'i': '3', 's': 'hi ho', 'x': '4.5', 'y': '5.5'}
744 >>> read_dict('test.ini',type_conv)
744 >>> read_dict('test.ini',type_conv)
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 >>> read_dict('test.ini',type_conv,purge=1)
746 >>> read_dict('test.ini',type_conv,purge=1)
747 {'i': 3, 's': 'hi ho', 'x': 4.5}
747 {'i': 3, 's': 'hi ho', 'x': 4.5}
748 """
748 """
749
749
750 # starting config
750 # starting config
751 opt.setdefault('purge',0)
751 opt.setdefault('purge',0)
752 opt.setdefault('fs',None) # field sep defaults to any whitespace
752 opt.setdefault('fs',None) # field sep defaults to any whitespace
753 opt.setdefault('strip',0)
753 opt.setdefault('strip',0)
754 opt.setdefault('warn',1)
754 opt.setdefault('warn',1)
755 opt.setdefault('no_empty',0)
755 opt.setdefault('no_empty',0)
756 opt.setdefault('unique','')
756 opt.setdefault('unique','')
757 if type(opt['unique']) in StringTypes:
757 if type(opt['unique']) in StringTypes:
758 unique_keys = qw(opt['unique'])
758 unique_keys = qw(opt['unique'])
759 elif type(opt['unique']) in (types.TupleType,types.ListType):
759 elif type(opt['unique']) in (types.TupleType,types.ListType):
760 unique_keys = opt['unique']
760 unique_keys = opt['unique']
761 else:
761 else:
762 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
762 raise ValueError, 'Unique keys must be given as a string, List or Tuple'
763
763
764 dict = {}
764 dict = {}
765 # first read in table of values as strings
765 # first read in table of values as strings
766 file = open(filename,'r')
766 file = open(filename,'r')
767 for line in file.readlines():
767 for line in file.readlines():
768 line = line.strip()
768 line = line.strip()
769 if len(line) and line[0]=='#': continue
769 if len(line) and line[0]=='#': continue
770 if len(line)>0:
770 if len(line)>0:
771 lsplit = line.split(opt['fs'],1)
771 lsplit = line.split(opt['fs'],1)
772 try:
772 try:
773 key,val = lsplit
773 key,val = lsplit
774 except ValueError:
774 except ValueError:
775 key,val = lsplit[0],''
775 key,val = lsplit[0],''
776 key = key.strip()
776 key = key.strip()
777 if opt['strip']: val = val.strip()
777 if opt['strip']: val = val.strip()
778 if val == "''" or val == '""': val = ''
778 if val == "''" or val == '""': val = ''
779 if opt['no_empty'] and (val=='' or val.isspace()):
779 if opt['no_empty'] and (val=='' or val.isspace()):
780 continue
780 continue
781 # if a key is found more than once in the file, build a list
781 # if a key is found more than once in the file, build a list
782 # unless it's in the 'unique' list. In that case, last found in file
782 # unless it's in the 'unique' list. In that case, last found in file
783 # takes precedence. User beware.
783 # takes precedence. User beware.
784 try:
784 try:
785 if dict[key] and key in unique_keys:
785 if dict[key] and key in unique_keys:
786 dict[key] = val
786 dict[key] = val
787 elif type(dict[key]) is types.ListType:
787 elif type(dict[key]) is types.ListType:
788 dict[key].append(val)
788 dict[key].append(val)
789 else:
789 else:
790 dict[key] = [dict[key],val]
790 dict[key] = [dict[key],val]
791 except KeyError:
791 except KeyError:
792 dict[key] = val
792 dict[key] = val
793 # purge if requested
793 # purge if requested
794 if opt['purge']:
794 if opt['purge']:
795 accepted_keys = qwflat(type_conv.values())
795 accepted_keys = qwflat(type_conv.values())
796 for key in dict.keys():
796 for key in dict.keys():
797 if key in accepted_keys: continue
797 if key in accepted_keys: continue
798 del(dict[key])
798 del(dict[key])
799 # now convert if requested
799 # now convert if requested
800 if type_conv==None: return dict
800 if type_conv==None: return dict
801 conversions = type_conv.keys()
801 conversions = type_conv.keys()
802 try: conversions.remove(None)
802 try: conversions.remove(None)
803 except: pass
803 except: pass
804 for convert in conversions:
804 for convert in conversions:
805 for val in qw(type_conv[convert]):
805 for val in qw(type_conv[convert]):
806 try:
806 try:
807 dict[val] = convert(dict[val])
807 dict[val] = convert(dict[val])
808 except KeyError,e:
808 except KeyError,e:
809 if opt['warn'] == 0:
809 if opt['warn'] == 0:
810 pass
810 pass
811 elif opt['warn'] == 1:
811 elif opt['warn'] == 1:
812 print >>sys.stderr, 'Warning: key',val,\
812 print >>sys.stderr, 'Warning: key',val,\
813 'not found in file',filename
813 'not found in file',filename
814 elif opt['warn'] == 2:
814 elif opt['warn'] == 2:
815 raise KeyError,e
815 raise KeyError,e
816 else:
816 else:
817 raise ValueError,'Warning level must be 0,1 or 2'
817 raise ValueError,'Warning level must be 0,1 or 2'
818
818
819 return dict
819 return dict
820
820
821 #----------------------------------------------------------------------------
821 #----------------------------------------------------------------------------
822 def flag_calls(func):
822 def flag_calls(func):
823 """Wrap a function to detect and flag when it gets called.
823 """Wrap a function to detect and flag when it gets called.
824
824
825 This is a decorator which takes a function and wraps it in a function with
825 This is a decorator which takes a function and wraps it in a function with
826 a 'called' attribute. wrapper.called is initialized to False.
826 a 'called' attribute. wrapper.called is initialized to False.
827
827
828 The wrapper.called attribute is set to False right before each call to the
828 The wrapper.called attribute is set to False right before each call to the
829 wrapped function, so if the call fails it remains False. After the call
829 wrapped function, so if the call fails it remains False. After the call
830 completes, wrapper.called is set to True and the output is returned.
830 completes, wrapper.called is set to True and the output is returned.
831
831
832 Testing for truth in wrapper.called allows you to determine if a call to
832 Testing for truth in wrapper.called allows you to determine if a call to
833 func() was attempted and succeeded."""
833 func() was attempted and succeeded."""
834
834
835 def wrapper(*args,**kw):
835 def wrapper(*args,**kw):
836 wrapper.called = False
836 wrapper.called = False
837 out = func(*args,**kw)
837 out = func(*args,**kw)
838 wrapper.called = True
838 wrapper.called = True
839 return out
839 return out
840
840
841 wrapper.called = False
841 wrapper.called = False
842 wrapper.__doc__ = func.__doc__
842 wrapper.__doc__ = func.__doc__
843 return wrapper
843 return wrapper
844
844
845 #----------------------------------------------------------------------------
845 #----------------------------------------------------------------------------
846 def dhook_wrap(func,*a,**k):
846 def dhook_wrap(func,*a,**k):
847 """Wrap a function call in a sys.displayhook controller.
847 """Wrap a function call in a sys.displayhook controller.
848
848
849 Returns a wrapper around func which calls func, with all its arguments and
849 Returns a wrapper around func which calls func, with all its arguments and
850 keywords unmodified, using the default sys.displayhook. Since IPython
850 keywords unmodified, using the default sys.displayhook. Since IPython
851 modifies sys.displayhook, it breaks the behavior of certain systems that
851 modifies sys.displayhook, it breaks the behavior of certain systems that
852 rely on the default behavior, notably doctest.
852 rely on the default behavior, notably doctest.
853 """
853 """
854
854
855 def f(*a,**k):
855 def f(*a,**k):
856
856
857 dhook_s = sys.displayhook
857 dhook_s = sys.displayhook
858 sys.displayhook = sys.__displayhook__
858 sys.displayhook = sys.__displayhook__
859 try:
859 try:
860 out = func(*a,**k)
860 out = func(*a,**k)
861 finally:
861 finally:
862 sys.displayhook = dhook_s
862 sys.displayhook = dhook_s
863
863
864 return out
864 return out
865
865
866 f.__doc__ = func.__doc__
866 f.__doc__ = func.__doc__
867 return f
867 return f
868
868
869 #----------------------------------------------------------------------------
869 #----------------------------------------------------------------------------
870 def doctest_reload():
870 def doctest_reload():
871 """Properly reload doctest to reuse it interactively.
871 """Properly reload doctest to reuse it interactively.
872
872
873 This routine:
873 This routine:
874
874
875 - reloads doctest
875 - reloads doctest
876
876
877 - resets its global 'master' attribute to None, so that multiple uses of
877 - resets its global 'master' attribute to None, so that multiple uses of
878 the module interactively don't produce cumulative reports.
878 the module interactively don't produce cumulative reports.
879
879
880 - Monkeypatches its core test runner method to protect it from IPython's
880 - Monkeypatches its core test runner method to protect it from IPython's
881 modified displayhook. Doctest expects the default displayhook behavior
881 modified displayhook. Doctest expects the default displayhook behavior
882 deep down, so our modification breaks it completely. For this reason, a
882 deep down, so our modification breaks it completely. For this reason, a
883 hard monkeypatch seems like a reasonable solution rather than asking
883 hard monkeypatch seems like a reasonable solution rather than asking
884 users to manually use a different doctest runner when under IPython."""
884 users to manually use a different doctest runner when under IPython."""
885
885
886 import doctest
886 import doctest
887 reload(doctest)
887 reload(doctest)
888 doctest.master=None
888 doctest.master=None
889
889
890 try:
890 try:
891 doctest.DocTestRunner
891 doctest.DocTestRunner
892 except AttributeError:
892 except AttributeError:
893 # This is only for python 2.3 compatibility, remove once we move to
893 # This is only for python 2.3 compatibility, remove once we move to
894 # 2.4 only.
894 # 2.4 only.
895 pass
895 pass
896 else:
896 else:
897 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
897 doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
898
898
899 #----------------------------------------------------------------------------
899 #----------------------------------------------------------------------------
900 class HomeDirError(Error):
900 class HomeDirError(Error):
901 pass
901 pass
902
902
903 def get_home_dir():
903 def get_home_dir():
904 """Return the closest possible equivalent to a 'home' directory.
904 """Return the closest possible equivalent to a 'home' directory.
905
905
906 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
906 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
907
907
908 Currently only Posix and NT are implemented, a HomeDirError exception is
908 Currently only Posix and NT are implemented, a HomeDirError exception is
909 raised for all other OSes. """
909 raised for all other OSes. """
910
910
911 isdir = os.path.isdir
911 isdir = os.path.isdir
912 env = os.environ
912 env = os.environ
913
913
914 # first, check py2exe distribution root directory for _ipython.
914 # first, check py2exe distribution root directory for _ipython.
915 # This overrides all. Normally does not exist.
915 # This overrides all. Normally does not exist.
916
916
917 if '\\library.zip\\' in IPython.__file__.lower():
917 if '\\library.zip\\' in IPython.__file__.lower():
918 root, rest = IPython.__file__.lower().split('library.zip')
918 root, rest = IPython.__file__.lower().split('library.zip')
919 if isdir(root + '_ipython'):
919 if isdir(root + '_ipython'):
920 os.environ["IPYKITROOT"] = root.rstrip('\\')
920 os.environ["IPYKITROOT"] = root.rstrip('\\')
921 return root
921 return root
922
922
923 try:
923 try:
924 homedir = env['HOME']
924 homedir = env['HOME']
925 if not isdir(homedir):
925 if not isdir(homedir):
926 # in case a user stuck some string which does NOT resolve to a
926 # in case a user stuck some string which does NOT resolve to a
927 # valid path, it's as good as if we hadn't foud it
927 # valid path, it's as good as if we hadn't foud it
928 raise KeyError
928 raise KeyError
929 return homedir
929 return homedir
930 except KeyError:
930 except KeyError:
931 if os.name == 'posix':
931 if os.name == 'posix':
932 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
932 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
933 elif os.name == 'nt':
933 elif os.name == 'nt':
934 # For some strange reason, win9x returns 'nt' for os.name.
934 # For some strange reason, win9x returns 'nt' for os.name.
935 try:
935 try:
936 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
936 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
937 if not isdir(homedir):
937 if not isdir(homedir):
938 homedir = os.path.join(env['USERPROFILE'])
938 homedir = os.path.join(env['USERPROFILE'])
939 if not isdir(homedir):
939 if not isdir(homedir):
940 raise HomeDirError
940 raise HomeDirError
941 return homedir
941 return homedir
942 except:
942 except:
943 try:
943 try:
944 # Use the registry to get the 'My Documents' folder.
944 # Use the registry to get the 'My Documents' folder.
945 import _winreg as wreg
945 import _winreg as wreg
946 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
946 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
947 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
947 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
948 homedir = wreg.QueryValueEx(key,'Personal')[0]
948 homedir = wreg.QueryValueEx(key,'Personal')[0]
949 key.Close()
949 key.Close()
950 if not isdir(homedir):
950 if not isdir(homedir):
951 e = ('Invalid "Personal" folder registry key '
951 e = ('Invalid "Personal" folder registry key '
952 'typically "My Documents".\n'
952 'typically "My Documents".\n'
953 'Value: %s\n'
953 'Value: %s\n'
954 'This is not a valid directory on your system.' %
954 'This is not a valid directory on your system.' %
955 homedir)
955 homedir)
956 raise HomeDirError(e)
956 raise HomeDirError(e)
957 return homedir
957 return homedir
958 except HomeDirError:
958 except HomeDirError:
959 raise
959 raise
960 except:
960 except:
961 return 'C:\\'
961 return 'C:\\'
962 elif os.name == 'dos':
962 elif os.name == 'dos':
963 # Desperate, may do absurd things in classic MacOS. May work under DOS.
963 # Desperate, may do absurd things in classic MacOS. May work under DOS.
964 return 'C:\\'
964 return 'C:\\'
965 else:
965 else:
966 raise HomeDirError,'support for your operating system not implemented.'
966 raise HomeDirError,'support for your operating system not implemented.'
967
967
968 #****************************************************************************
968 #****************************************************************************
969 # strings and text
969 # strings and text
970
970
971 class LSString(str):
971 class LSString(str):
972 """String derivative with a special access attributes.
972 """String derivative with a special access attributes.
973
973
974 These are normal strings, but with the special attributes:
974 These are normal strings, but with the special attributes:
975
975
976 .l (or .list) : value as list (split on newlines).
976 .l (or .list) : value as list (split on newlines).
977 .n (or .nlstr): original value (the string itself).
977 .n (or .nlstr): original value (the string itself).
978 .s (or .spstr): value as whitespace-separated string.
978 .s (or .spstr): value as whitespace-separated string.
979 .p (or .paths): list of path objects
979 .p (or .paths): list of path objects
980
980
981 Any values which require transformations are computed only once and
981 Any values which require transformations are computed only once and
982 cached.
982 cached.
983
983
984 Such strings are very useful to efficiently interact with the shell, which
984 Such strings are very useful to efficiently interact with the shell, which
985 typically only understands whitespace-separated options for commands."""
985 typically only understands whitespace-separated options for commands."""
986
986
987 def get_list(self):
987 def get_list(self):
988 try:
988 try:
989 return self.__list
989 return self.__list
990 except AttributeError:
990 except AttributeError:
991 self.__list = self.split('\n')
991 self.__list = self.split('\n')
992 return self.__list
992 return self.__list
993
993
994 l = list = property(get_list)
994 l = list = property(get_list)
995
995
996 def get_spstr(self):
996 def get_spstr(self):
997 try:
997 try:
998 return self.__spstr
998 return self.__spstr
999 except AttributeError:
999 except AttributeError:
1000 self.__spstr = self.replace('\n',' ')
1000 self.__spstr = self.replace('\n',' ')
1001 return self.__spstr
1001 return self.__spstr
1002
1002
1003 s = spstr = property(get_spstr)
1003 s = spstr = property(get_spstr)
1004
1004
1005 def get_nlstr(self):
1005 def get_nlstr(self):
1006 return self
1006 return self
1007
1007
1008 n = nlstr = property(get_nlstr)
1008 n = nlstr = property(get_nlstr)
1009
1009
1010 def get_paths(self):
1010 def get_paths(self):
1011 try:
1011 try:
1012 return self.__paths
1012 return self.__paths
1013 except AttributeError:
1013 except AttributeError:
1014 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1014 self.__paths = [path(p) for p in self.split('\n') if os.path.exists(p)]
1015 return self.__paths
1015 return self.__paths
1016
1016
1017 p = paths = property(get_paths)
1017 p = paths = property(get_paths)
1018
1018
1019 def print_lsstring(arg):
1019 def print_lsstring(arg):
1020 """ Prettier (non-repr-like) and more informative printer for LSString """
1020 """ Prettier (non-repr-like) and more informative printer for LSString """
1021 print "LSString (.p, .n, .l, .s available). Value:"
1021 print "LSString (.p, .n, .l, .s available). Value:"
1022 print arg
1022 print arg
1023
1023
1024 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1024 print_lsstring = result_display.when_type(LSString)(print_lsstring)
1025
1025
1026 #----------------------------------------------------------------------------
1026 #----------------------------------------------------------------------------
1027 class SList(list):
1027 class SList(list):
1028 """List derivative with a special access attributes.
1028 """List derivative with a special access attributes.
1029
1029
1030 These are normal lists, but with the special attributes:
1030 These are normal lists, but with the special attributes:
1031
1031
1032 .l (or .list) : value as list (the list itself).
1032 .l (or .list) : value as list (the list itself).
1033 .n (or .nlstr): value as a string, joined on newlines.
1033 .n (or .nlstr): value as a string, joined on newlines.
1034 .s (or .spstr): value as a string, joined on spaces.
1034 .s (or .spstr): value as a string, joined on spaces.
1035 .p (or .paths): list of path objects
1035 .p (or .paths): list of path objects
1036
1036
1037 Any values which require transformations are computed only once and
1037 Any values which require transformations are computed only once and
1038 cached."""
1038 cached."""
1039
1039
1040 def get_list(self):
1040 def get_list(self):
1041 return self
1041 return self
1042
1042
1043 l = list = property(get_list)
1043 l = list = property(get_list)
1044
1044
1045 def get_spstr(self):
1045 def get_spstr(self):
1046 try:
1046 try:
1047 return self.__spstr
1047 return self.__spstr
1048 except AttributeError:
1048 except AttributeError:
1049 self.__spstr = ' '.join(self)
1049 self.__spstr = ' '.join(self)
1050 return self.__spstr
1050 return self.__spstr
1051
1051
1052 s = spstr = property(get_spstr)
1052 s = spstr = property(get_spstr)
1053
1053
1054 def get_nlstr(self):
1054 def get_nlstr(self):
1055 try:
1055 try:
1056 return self.__nlstr
1056 return self.__nlstr
1057 except AttributeError:
1057 except AttributeError:
1058 self.__nlstr = '\n'.join(self)
1058 self.__nlstr = '\n'.join(self)
1059 return self.__nlstr
1059 return self.__nlstr
1060
1060
1061 n = nlstr = property(get_nlstr)
1061 n = nlstr = property(get_nlstr)
1062
1062
1063 def get_paths(self):
1063 def get_paths(self):
1064 try:
1064 try:
1065 return self.__paths
1065 return self.__paths
1066 except AttributeError:
1066 except AttributeError:
1067 self.__paths = [path(p) for p in self if os.path.exists(p)]
1067 self.__paths = [path(p) for p in self if os.path.exists(p)]
1068 return self.__paths
1068 return self.__paths
1069
1069
1070 p = paths = property(get_paths)
1070 p = paths = property(get_paths)
1071
1071
1072 def grep(self, pattern, prune = False, field = None):
1072 def grep(self, pattern, prune = False, field = None):
1073 """ Return all strings matching 'pattern' (a regex or callable)
1073 """ Return all strings matching 'pattern' (a regex or callable)
1074
1074
1075 This is case-insensitive. If prune is true, return all items
1075 This is case-insensitive. If prune is true, return all items
1076 NOT matching the pattern.
1076 NOT matching the pattern.
1077
1077
1078 If field is specified, the match must occur in the specified
1078 If field is specified, the match must occur in the specified
1079 whitespace-separated field.
1079 whitespace-separated field.
1080
1080
1081 Examples::
1081 Examples::
1082
1082
1083 a.grep( lambda x: x.startswith('C') )
1083 a.grep( lambda x: x.startswith('C') )
1084 a.grep('Cha.*log', prune=1)
1084 a.grep('Cha.*log', prune=1)
1085 a.grep('chm', field=-1)
1085 a.grep('chm', field=-1)
1086 """
1086 """
1087
1087
1088 def match_target(s):
1088 def match_target(s):
1089 if field is None:
1089 if field is None:
1090 return s
1090 return s
1091 parts = s.split()
1091 parts = s.split()
1092 try:
1092 try:
1093 tgt = parts[field]
1093 tgt = parts[field]
1094 return tgt
1094 return tgt
1095 except IndexError:
1095 except IndexError:
1096 return ""
1096 return ""
1097
1097
1098 if isinstance(pattern, basestring):
1098 if isinstance(pattern, basestring):
1099 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1099 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
1100 else:
1100 else:
1101 pred = pattern
1101 pred = pattern
1102 if not prune:
1102 if not prune:
1103 return SList([el for el in self if pred(match_target(el))])
1103 return SList([el for el in self if pred(match_target(el))])
1104 else:
1104 else:
1105 return SList([el for el in self if not pred(match_target(el))])
1105 return SList([el for el in self if not pred(match_target(el))])
1106 def fields(self, *fields):
1106 def fields(self, *fields):
1107 """ Collect whitespace-separated fields from string list
1107 """ Collect whitespace-separated fields from string list
1108
1108
1109 Allows quick awk-like usage of string lists.
1109 Allows quick awk-like usage of string lists.
1110
1110
1111 Example data (in var a, created by 'a = !ls -l')::
1111 Example data (in var a, created by 'a = !ls -l')::
1112 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1112 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
1113 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1113 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
1114
1114
1115 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1115 a.fields(0) is ['-rwxrwxrwx', 'drwxrwxrwx+']
1116 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1116 a.fields(1,0) is ['1 -rwxrwxrwx', '6 drwxrwxrwx+']
1117 (note the joining by space).
1117 (note the joining by space).
1118 a.fields(-1) is ['ChangeLog', 'IPython']
1118 a.fields(-1) is ['ChangeLog', 'IPython']
1119
1119
1120 IndexErrors are ignored.
1120 IndexErrors are ignored.
1121
1121
1122 Without args, fields() just split()'s the strings.
1122 Without args, fields() just split()'s the strings.
1123 """
1123 """
1124 if len(fields) == 0:
1124 if len(fields) == 0:
1125 return [el.split() for el in self]
1125 return [el.split() for el in self]
1126
1126
1127 res = SList()
1127 res = SList()
1128 for el in [f.split() for f in self]:
1128 for el in [f.split() for f in self]:
1129 lineparts = []
1129 lineparts = []
1130
1130
1131 for fd in fields:
1131 for fd in fields:
1132 try:
1132 try:
1133 lineparts.append(el[fd])
1133 lineparts.append(el[fd])
1134 except IndexError:
1134 except IndexError:
1135 pass
1135 pass
1136 if lineparts:
1136 if lineparts:
1137 res.append(" ".join(lineparts))
1137 res.append(" ".join(lineparts))
1138
1138
1139 return res
1139 return res
1140
1140 def sort(self,field= None, nums = False):
1141
1141 """ sort by specified fields (see fields())
1142
1142
1143 Example::
1144 a.sort(1, nums = True)
1145
1146 Sorts a by second field, in numerical order (so that 21 > 3)
1147
1148 """
1143
1149
1150 #decorate, sort, undecorate
1151 if field is not None:
1152 dsu = [[SList([line]).fields(field), line] for line in self]
1153 else:
1154 dsu = [[line, line] for line in self]
1155 if nums:
1156 for i in range(len(dsu)):
1157 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
1158 try:
1159 n = int(numstr)
1160 except ValueError:
1161 n = 0;
1162 dsu[i][0] = n
1163
1164
1165 dsu.sort()
1166 return SList([t[1] for t in dsu])
1144
1167
1145 def print_slist(arg):
1168 def print_slist(arg):
1146 """ Prettier (non-repr-like) and more informative printer for SList """
1169 """ Prettier (non-repr-like) and more informative printer for SList """
1147 print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:"
1170 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1171 if hasattr(arg, 'hideonce') and arg.hideonce:
1172 arg.hideonce = False
1173 return
1174
1148 nlprint(arg)
1175 nlprint(arg)
1149
1176
1150 print_slist = result_display.when_type(SList)(print_slist)
1177 print_slist = result_display.when_type(SList)(print_slist)
1151
1178
1152
1179
1153
1180
1154 #----------------------------------------------------------------------------
1181 #----------------------------------------------------------------------------
1155 def esc_quotes(strng):
1182 def esc_quotes(strng):
1156 """Return the input string with single and double quotes escaped out"""
1183 """Return the input string with single and double quotes escaped out"""
1157
1184
1158 return strng.replace('"','\\"').replace("'","\\'")
1185 return strng.replace('"','\\"').replace("'","\\'")
1159
1186
1160 #----------------------------------------------------------------------------
1187 #----------------------------------------------------------------------------
1161 def make_quoted_expr(s):
1188 def make_quoted_expr(s):
1162 """Return string s in appropriate quotes, using raw string if possible.
1189 """Return string s in appropriate quotes, using raw string if possible.
1163
1190
1164 Effectively this turns string: cd \ao\ao\
1191 Effectively this turns string: cd \ao\ao\
1165 to: r"cd \ao\ao\_"[:-1]
1192 to: r"cd \ao\ao\_"[:-1]
1166
1193
1167 Note the use of raw string and padding at the end to allow trailing backslash.
1194 Note the use of raw string and padding at the end to allow trailing backslash.
1168
1195
1169 """
1196 """
1170
1197
1171 tail = ''
1198 tail = ''
1172 tailpadding = ''
1199 tailpadding = ''
1173 raw = ''
1200 raw = ''
1174 if "\\" in s:
1201 if "\\" in s:
1175 raw = 'r'
1202 raw = 'r'
1176 if s.endswith('\\'):
1203 if s.endswith('\\'):
1177 tail = '[:-1]'
1204 tail = '[:-1]'
1178 tailpadding = '_'
1205 tailpadding = '_'
1179 if '"' not in s:
1206 if '"' not in s:
1180 quote = '"'
1207 quote = '"'
1181 elif "'" not in s:
1208 elif "'" not in s:
1182 quote = "'"
1209 quote = "'"
1183 elif '"""' not in s and not s.endswith('"'):
1210 elif '"""' not in s and not s.endswith('"'):
1184 quote = '"""'
1211 quote = '"""'
1185 elif "'''" not in s and not s.endswith("'"):
1212 elif "'''" not in s and not s.endswith("'"):
1186 quote = "'''"
1213 quote = "'''"
1187 else:
1214 else:
1188 # give up, backslash-escaped string will do
1215 # give up, backslash-escaped string will do
1189 return '"%s"' % esc_quotes(s)
1216 return '"%s"' % esc_quotes(s)
1190 res = raw + quote + s + tailpadding + quote + tail
1217 res = raw + quote + s + tailpadding + quote + tail
1191 return res
1218 return res
1192
1219
1193
1220
1194 #----------------------------------------------------------------------------
1221 #----------------------------------------------------------------------------
1195 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1222 def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'):
1196 """Take multiple lines of input.
1223 """Take multiple lines of input.
1197
1224
1198 A list with each line of input as a separate element is returned when a
1225 A list with each line of input as a separate element is returned when a
1199 termination string is entered (defaults to a single '.'). Input can also
1226 termination string is entered (defaults to a single '.'). Input can also
1200 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1227 terminate via EOF (^D in Unix, ^Z-RET in Windows).
1201
1228
1202 Lines of input which end in \\ are joined into single entries (and a
1229 Lines of input which end in \\ are joined into single entries (and a
1203 secondary continuation prompt is issued as long as the user terminates
1230 secondary continuation prompt is issued as long as the user terminates
1204 lines with \\). This allows entering very long strings which are still
1231 lines with \\). This allows entering very long strings which are still
1205 meant to be treated as single entities.
1232 meant to be treated as single entities.
1206 """
1233 """
1207
1234
1208 try:
1235 try:
1209 if header:
1236 if header:
1210 header += '\n'
1237 header += '\n'
1211 lines = [raw_input(header + ps1)]
1238 lines = [raw_input(header + ps1)]
1212 except EOFError:
1239 except EOFError:
1213 return []
1240 return []
1214 terminate = [terminate_str]
1241 terminate = [terminate_str]
1215 try:
1242 try:
1216 while lines[-1:] != terminate:
1243 while lines[-1:] != terminate:
1217 new_line = raw_input(ps1)
1244 new_line = raw_input(ps1)
1218 while new_line.endswith('\\'):
1245 while new_line.endswith('\\'):
1219 new_line = new_line[:-1] + raw_input(ps2)
1246 new_line = new_line[:-1] + raw_input(ps2)
1220 lines.append(new_line)
1247 lines.append(new_line)
1221
1248
1222 return lines[:-1] # don't return the termination command
1249 return lines[:-1] # don't return the termination command
1223 except EOFError:
1250 except EOFError:
1224 print
1251 print
1225 return lines
1252 return lines
1226
1253
1227 #----------------------------------------------------------------------------
1254 #----------------------------------------------------------------------------
1228 def raw_input_ext(prompt='', ps2='... '):
1255 def raw_input_ext(prompt='', ps2='... '):
1229 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1256 """Similar to raw_input(), but accepts extended lines if input ends with \\."""
1230
1257
1231 line = raw_input(prompt)
1258 line = raw_input(prompt)
1232 while line.endswith('\\'):
1259 while line.endswith('\\'):
1233 line = line[:-1] + raw_input(ps2)
1260 line = line[:-1] + raw_input(ps2)
1234 return line
1261 return line
1235
1262
1236 #----------------------------------------------------------------------------
1263 #----------------------------------------------------------------------------
1237 def ask_yes_no(prompt,default=None):
1264 def ask_yes_no(prompt,default=None):
1238 """Asks a question and returns a boolean (y/n) answer.
1265 """Asks a question and returns a boolean (y/n) answer.
1239
1266
1240 If default is given (one of 'y','n'), it is used if the user input is
1267 If default is given (one of 'y','n'), it is used if the user input is
1241 empty. Otherwise the question is repeated until an answer is given.
1268 empty. Otherwise the question is repeated until an answer is given.
1242
1269
1243 An EOF is treated as the default answer. If there is no default, an
1270 An EOF is treated as the default answer. If there is no default, an
1244 exception is raised to prevent infinite loops.
1271 exception is raised to prevent infinite loops.
1245
1272
1246 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1273 Valid answers are: y/yes/n/no (match is not case sensitive)."""
1247
1274
1248 answers = {'y':True,'n':False,'yes':True,'no':False}
1275 answers = {'y':True,'n':False,'yes':True,'no':False}
1249 ans = None
1276 ans = None
1250 while ans not in answers.keys():
1277 while ans not in answers.keys():
1251 try:
1278 try:
1252 ans = raw_input(prompt+' ').lower()
1279 ans = raw_input(prompt+' ').lower()
1253 if not ans: # response was an empty string
1280 if not ans: # response was an empty string
1254 ans = default
1281 ans = default
1255 except KeyboardInterrupt:
1282 except KeyboardInterrupt:
1256 pass
1283 pass
1257 except EOFError:
1284 except EOFError:
1258 if default in answers.keys():
1285 if default in answers.keys():
1259 ans = default
1286 ans = default
1260 print
1287 print
1261 else:
1288 else:
1262 raise
1289 raise
1263
1290
1264 return answers[ans]
1291 return answers[ans]
1265
1292
1266 #----------------------------------------------------------------------------
1293 #----------------------------------------------------------------------------
1267 def marquee(txt='',width=78,mark='*'):
1294 def marquee(txt='',width=78,mark='*'):
1268 """Return the input string centered in a 'marquee'."""
1295 """Return the input string centered in a 'marquee'."""
1269 if not txt:
1296 if not txt:
1270 return (mark*width)[:width]
1297 return (mark*width)[:width]
1271 nmark = (width-len(txt)-2)/len(mark)/2
1298 nmark = (width-len(txt)-2)/len(mark)/2
1272 if nmark < 0: nmark =0
1299 if nmark < 0: nmark =0
1273 marks = mark*nmark
1300 marks = mark*nmark
1274 return '%s %s %s' % (marks,txt,marks)
1301 return '%s %s %s' % (marks,txt,marks)
1275
1302
1276 #----------------------------------------------------------------------------
1303 #----------------------------------------------------------------------------
1277 class EvalDict:
1304 class EvalDict:
1278 """
1305 """
1279 Emulate a dict which evaluates its contents in the caller's frame.
1306 Emulate a dict which evaluates its contents in the caller's frame.
1280
1307
1281 Usage:
1308 Usage:
1282 >>>number = 19
1309 >>>number = 19
1283 >>>text = "python"
1310 >>>text = "python"
1284 >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1311 >>>print "%(text.capitalize())s %(number/9.0).1f rules!" % EvalDict()
1285 """
1312 """
1286
1313
1287 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1314 # This version is due to sismex01@hebmex.com on c.l.py, and is basically a
1288 # modified (shorter) version of:
1315 # modified (shorter) version of:
1289 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1316 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66018 by
1290 # Skip Montanaro (skip@pobox.com).
1317 # Skip Montanaro (skip@pobox.com).
1291
1318
1292 def __getitem__(self, name):
1319 def __getitem__(self, name):
1293 frame = sys._getframe(1)
1320 frame = sys._getframe(1)
1294 return eval(name, frame.f_globals, frame.f_locals)
1321 return eval(name, frame.f_globals, frame.f_locals)
1295
1322
1296 EvalString = EvalDict # for backwards compatibility
1323 EvalString = EvalDict # for backwards compatibility
1297 #----------------------------------------------------------------------------
1324 #----------------------------------------------------------------------------
1298 def qw(words,flat=0,sep=None,maxsplit=-1):
1325 def qw(words,flat=0,sep=None,maxsplit=-1):
1299 """Similar to Perl's qw() operator, but with some more options.
1326 """Similar to Perl's qw() operator, but with some more options.
1300
1327
1301 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1328 qw(words,flat=0,sep=' ',maxsplit=-1) -> words.split(sep,maxsplit)
1302
1329
1303 words can also be a list itself, and with flat=1, the output will be
1330 words can also be a list itself, and with flat=1, the output will be
1304 recursively flattened. Examples:
1331 recursively flattened. Examples:
1305
1332
1306 >>> qw('1 2')
1333 >>> qw('1 2')
1307 ['1', '2']
1334 ['1', '2']
1308 >>> qw(['a b','1 2',['m n','p q']])
1335 >>> qw(['a b','1 2',['m n','p q']])
1309 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1336 [['a', 'b'], ['1', '2'], [['m', 'n'], ['p', 'q']]]
1310 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1337 >>> qw(['a b','1 2',['m n','p q']],flat=1)
1311 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """
1338 ['a', 'b', '1', '2', 'm', 'n', 'p', 'q'] """
1312
1339
1313 if type(words) in StringTypes:
1340 if type(words) in StringTypes:
1314 return [word.strip() for word in words.split(sep,maxsplit)
1341 return [word.strip() for word in words.split(sep,maxsplit)
1315 if word and not word.isspace() ]
1342 if word and not word.isspace() ]
1316 if flat:
1343 if flat:
1317 return flatten(map(qw,words,[1]*len(words)))
1344 return flatten(map(qw,words,[1]*len(words)))
1318 return map(qw,words)
1345 return map(qw,words)
1319
1346
1320 #----------------------------------------------------------------------------
1347 #----------------------------------------------------------------------------
1321 def qwflat(words,sep=None,maxsplit=-1):
1348 def qwflat(words,sep=None,maxsplit=-1):
1322 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1349 """Calls qw(words) in flat mode. It's just a convenient shorthand."""
1323 return qw(words,1,sep,maxsplit)
1350 return qw(words,1,sep,maxsplit)
1324
1351
1325 #----------------------------------------------------------------------------
1352 #----------------------------------------------------------------------------
1326 def qw_lol(indata):
1353 def qw_lol(indata):
1327 """qw_lol('a b') -> [['a','b']],
1354 """qw_lol('a b') -> [['a','b']],
1328 otherwise it's just a call to qw().
1355 otherwise it's just a call to qw().
1329
1356
1330 We need this to make sure the modules_some keys *always* end up as a
1357 We need this to make sure the modules_some keys *always* end up as a
1331 list of lists."""
1358 list of lists."""
1332
1359
1333 if type(indata) in StringTypes:
1360 if type(indata) in StringTypes:
1334 return [qw(indata)]
1361 return [qw(indata)]
1335 else:
1362 else:
1336 return qw(indata)
1363 return qw(indata)
1337
1364
1338 #-----------------------------------------------------------------------------
1365 #-----------------------------------------------------------------------------
1339 def list_strings(arg):
1366 def list_strings(arg):
1340 """Always return a list of strings, given a string or list of strings
1367 """Always return a list of strings, given a string or list of strings
1341 as input."""
1368 as input."""
1342
1369
1343 if type(arg) in StringTypes: return [arg]
1370 if type(arg) in StringTypes: return [arg]
1344 else: return arg
1371 else: return arg
1345
1372
1346 #----------------------------------------------------------------------------
1373 #----------------------------------------------------------------------------
1347 def grep(pat,list,case=1):
1374 def grep(pat,list,case=1):
1348 """Simple minded grep-like function.
1375 """Simple minded grep-like function.
1349 grep(pat,list) returns occurrences of pat in list, None on failure.
1376 grep(pat,list) returns occurrences of pat in list, None on failure.
1350
1377
1351 It only does simple string matching, with no support for regexps. Use the
1378 It only does simple string matching, with no support for regexps. Use the
1352 option case=0 for case-insensitive matching."""
1379 option case=0 for case-insensitive matching."""
1353
1380
1354 # This is pretty crude. At least it should implement copying only references
1381 # This is pretty crude. At least it should implement copying only references
1355 # to the original data in case it's big. Now it copies the data for output.
1382 # to the original data in case it's big. Now it copies the data for output.
1356 out=[]
1383 out=[]
1357 if case:
1384 if case:
1358 for term in list:
1385 for term in list:
1359 if term.find(pat)>-1: out.append(term)
1386 if term.find(pat)>-1: out.append(term)
1360 else:
1387 else:
1361 lpat=pat.lower()
1388 lpat=pat.lower()
1362 for term in list:
1389 for term in list:
1363 if term.lower().find(lpat)>-1: out.append(term)
1390 if term.lower().find(lpat)>-1: out.append(term)
1364
1391
1365 if len(out): return out
1392 if len(out): return out
1366 else: return None
1393 else: return None
1367
1394
1368 #----------------------------------------------------------------------------
1395 #----------------------------------------------------------------------------
1369 def dgrep(pat,*opts):
1396 def dgrep(pat,*opts):
1370 """Return grep() on dir()+dir(__builtins__).
1397 """Return grep() on dir()+dir(__builtins__).
1371
1398
1372 A very common use of grep() when working interactively."""
1399 A very common use of grep() when working interactively."""
1373
1400
1374 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1401 return grep(pat,dir(__main__)+dir(__main__.__builtins__),*opts)
1375
1402
1376 #----------------------------------------------------------------------------
1403 #----------------------------------------------------------------------------
1377 def idgrep(pat):
1404 def idgrep(pat):
1378 """Case-insensitive dgrep()"""
1405 """Case-insensitive dgrep()"""
1379
1406
1380 return dgrep(pat,0)
1407 return dgrep(pat,0)
1381
1408
1382 #----------------------------------------------------------------------------
1409 #----------------------------------------------------------------------------
1383 def igrep(pat,list):
1410 def igrep(pat,list):
1384 """Synonym for case-insensitive grep."""
1411 """Synonym for case-insensitive grep."""
1385
1412
1386 return grep(pat,list,case=0)
1413 return grep(pat,list,case=0)
1387
1414
1388 #----------------------------------------------------------------------------
1415 #----------------------------------------------------------------------------
1389 def indent(str,nspaces=4,ntabs=0):
1416 def indent(str,nspaces=4,ntabs=0):
1390 """Indent a string a given number of spaces or tabstops.
1417 """Indent a string a given number of spaces or tabstops.
1391
1418
1392 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1419 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
1393 """
1420 """
1394 if str is None:
1421 if str is None:
1395 return
1422 return
1396 ind = '\t'*ntabs+' '*nspaces
1423 ind = '\t'*ntabs+' '*nspaces
1397 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1424 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
1398 if outstr.endswith(os.linesep+ind):
1425 if outstr.endswith(os.linesep+ind):
1399 return outstr[:-len(ind)]
1426 return outstr[:-len(ind)]
1400 else:
1427 else:
1401 return outstr
1428 return outstr
1402
1429
1403 #-----------------------------------------------------------------------------
1430 #-----------------------------------------------------------------------------
1404 def native_line_ends(filename,backup=1):
1431 def native_line_ends(filename,backup=1):
1405 """Convert (in-place) a file to line-ends native to the current OS.
1432 """Convert (in-place) a file to line-ends native to the current OS.
1406
1433
1407 If the optional backup argument is given as false, no backup of the
1434 If the optional backup argument is given as false, no backup of the
1408 original file is left. """
1435 original file is left. """
1409
1436
1410 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1437 backup_suffixes = {'posix':'~','dos':'.bak','nt':'.bak','mac':'.bak'}
1411
1438
1412 bak_filename = filename + backup_suffixes[os.name]
1439 bak_filename = filename + backup_suffixes[os.name]
1413
1440
1414 original = open(filename).read()
1441 original = open(filename).read()
1415 shutil.copy2(filename,bak_filename)
1442 shutil.copy2(filename,bak_filename)
1416 try:
1443 try:
1417 new = open(filename,'wb')
1444 new = open(filename,'wb')
1418 new.write(os.linesep.join(original.splitlines()))
1445 new.write(os.linesep.join(original.splitlines()))
1419 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1446 new.write(os.linesep) # ALWAYS put an eol at the end of the file
1420 new.close()
1447 new.close()
1421 except:
1448 except:
1422 os.rename(bak_filename,filename)
1449 os.rename(bak_filename,filename)
1423 if not backup:
1450 if not backup:
1424 try:
1451 try:
1425 os.remove(bak_filename)
1452 os.remove(bak_filename)
1426 except:
1453 except:
1427 pass
1454 pass
1428
1455
1429 #----------------------------------------------------------------------------
1456 #----------------------------------------------------------------------------
1430 def get_pager_cmd(pager_cmd = None):
1457 def get_pager_cmd(pager_cmd = None):
1431 """Return a pager command.
1458 """Return a pager command.
1432
1459
1433 Makes some attempts at finding an OS-correct one."""
1460 Makes some attempts at finding an OS-correct one."""
1434
1461
1435 if os.name == 'posix':
1462 if os.name == 'posix':
1436 default_pager_cmd = 'less -r' # -r for color control sequences
1463 default_pager_cmd = 'less -r' # -r for color control sequences
1437 elif os.name in ['nt','dos']:
1464 elif os.name in ['nt','dos']:
1438 default_pager_cmd = 'type'
1465 default_pager_cmd = 'type'
1439
1466
1440 if pager_cmd is None:
1467 if pager_cmd is None:
1441 try:
1468 try:
1442 pager_cmd = os.environ['PAGER']
1469 pager_cmd = os.environ['PAGER']
1443 except:
1470 except:
1444 pager_cmd = default_pager_cmd
1471 pager_cmd = default_pager_cmd
1445 return pager_cmd
1472 return pager_cmd
1446
1473
1447 #-----------------------------------------------------------------------------
1474 #-----------------------------------------------------------------------------
1448 def get_pager_start(pager,start):
1475 def get_pager_start(pager,start):
1449 """Return the string for paging files with an offset.
1476 """Return the string for paging files with an offset.
1450
1477
1451 This is the '+N' argument which less and more (under Unix) accept.
1478 This is the '+N' argument which less and more (under Unix) accept.
1452 """
1479 """
1453
1480
1454 if pager in ['less','more']:
1481 if pager in ['less','more']:
1455 if start:
1482 if start:
1456 start_string = '+' + str(start)
1483 start_string = '+' + str(start)
1457 else:
1484 else:
1458 start_string = ''
1485 start_string = ''
1459 else:
1486 else:
1460 start_string = ''
1487 start_string = ''
1461 return start_string
1488 return start_string
1462
1489
1463 #----------------------------------------------------------------------------
1490 #----------------------------------------------------------------------------
1464 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1491 # (X)emacs on W32 doesn't like to be bypassed with msvcrt.getch()
1465 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1492 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
1466 import msvcrt
1493 import msvcrt
1467 def page_more():
1494 def page_more():
1468 """ Smart pausing between pages
1495 """ Smart pausing between pages
1469
1496
1470 @return: True if need print more lines, False if quit
1497 @return: True if need print more lines, False if quit
1471 """
1498 """
1472 Term.cout.write('---Return to continue, q to quit--- ')
1499 Term.cout.write('---Return to continue, q to quit--- ')
1473 ans = msvcrt.getch()
1500 ans = msvcrt.getch()
1474 if ans in ("q", "Q"):
1501 if ans in ("q", "Q"):
1475 result = False
1502 result = False
1476 else:
1503 else:
1477 result = True
1504 result = True
1478 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1505 Term.cout.write("\b"*37 + " "*37 + "\b"*37)
1479 return result
1506 return result
1480 else:
1507 else:
1481 def page_more():
1508 def page_more():
1482 ans = raw_input('---Return to continue, q to quit--- ')
1509 ans = raw_input('---Return to continue, q to quit--- ')
1483 if ans.lower().startswith('q'):
1510 if ans.lower().startswith('q'):
1484 return False
1511 return False
1485 else:
1512 else:
1486 return True
1513 return True
1487
1514
1488 esc_re = re.compile(r"(\x1b[^m]+m)")
1515 esc_re = re.compile(r"(\x1b[^m]+m)")
1489
1516
1490 def page_dumb(strng,start=0,screen_lines=25):
1517 def page_dumb(strng,start=0,screen_lines=25):
1491 """Very dumb 'pager' in Python, for when nothing else works.
1518 """Very dumb 'pager' in Python, for when nothing else works.
1492
1519
1493 Only moves forward, same interface as page(), except for pager_cmd and
1520 Only moves forward, same interface as page(), except for pager_cmd and
1494 mode."""
1521 mode."""
1495
1522
1496 out_ln = strng.splitlines()[start:]
1523 out_ln = strng.splitlines()[start:]
1497 screens = chop(out_ln,screen_lines-1)
1524 screens = chop(out_ln,screen_lines-1)
1498 if len(screens) == 1:
1525 if len(screens) == 1:
1499 print >>Term.cout, os.linesep.join(screens[0])
1526 print >>Term.cout, os.linesep.join(screens[0])
1500 else:
1527 else:
1501 last_escape = ""
1528 last_escape = ""
1502 for scr in screens[0:-1]:
1529 for scr in screens[0:-1]:
1503 hunk = os.linesep.join(scr)
1530 hunk = os.linesep.join(scr)
1504 print >>Term.cout, last_escape + hunk
1531 print >>Term.cout, last_escape + hunk
1505 if not page_more():
1532 if not page_more():
1506 return
1533 return
1507 esc_list = esc_re.findall(hunk)
1534 esc_list = esc_re.findall(hunk)
1508 if len(esc_list) > 0:
1535 if len(esc_list) > 0:
1509 last_escape = esc_list[-1]
1536 last_escape = esc_list[-1]
1510 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1537 print >>Term.cout, last_escape + os.linesep.join(screens[-1])
1511
1538
1512 #----------------------------------------------------------------------------
1539 #----------------------------------------------------------------------------
1513 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1540 def page(strng,start=0,screen_lines=0,pager_cmd = None):
1514 """Print a string, piping through a pager after a certain length.
1541 """Print a string, piping through a pager after a certain length.
1515
1542
1516 The screen_lines parameter specifies the number of *usable* lines of your
1543 The screen_lines parameter specifies the number of *usable* lines of your
1517 terminal screen (total lines minus lines you need to reserve to show other
1544 terminal screen (total lines minus lines you need to reserve to show other
1518 information).
1545 information).
1519
1546
1520 If you set screen_lines to a number <=0, page() will try to auto-determine
1547 If you set screen_lines to a number <=0, page() will try to auto-determine
1521 your screen size and will only use up to (screen_size+screen_lines) for
1548 your screen size and will only use up to (screen_size+screen_lines) for
1522 printing, paging after that. That is, if you want auto-detection but need
1549 printing, paging after that. That is, if you want auto-detection but need
1523 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1550 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
1524 auto-detection without any lines reserved simply use screen_lines = 0.
1551 auto-detection without any lines reserved simply use screen_lines = 0.
1525
1552
1526 If a string won't fit in the allowed lines, it is sent through the
1553 If a string won't fit in the allowed lines, it is sent through the
1527 specified pager command. If none given, look for PAGER in the environment,
1554 specified pager command. If none given, look for PAGER in the environment,
1528 and ultimately default to less.
1555 and ultimately default to less.
1529
1556
1530 If no system pager works, the string is sent through a 'dumb pager'
1557 If no system pager works, the string is sent through a 'dumb pager'
1531 written in python, very simplistic.
1558 written in python, very simplistic.
1532 """
1559 """
1533
1560
1534 # Some routines may auto-compute start offsets incorrectly and pass a
1561 # Some routines may auto-compute start offsets incorrectly and pass a
1535 # negative value. Offset to 0 for robustness.
1562 # negative value. Offset to 0 for robustness.
1536 start = max(0,start)
1563 start = max(0,start)
1537
1564
1538 # first, try the hook
1565 # first, try the hook
1539 ip = IPython.ipapi.get()
1566 ip = IPython.ipapi.get()
1540 if ip:
1567 if ip:
1541 try:
1568 try:
1542 ip.IP.hooks.show_in_pager(strng)
1569 ip.IP.hooks.show_in_pager(strng)
1543 return
1570 return
1544 except IPython.ipapi.TryNext:
1571 except IPython.ipapi.TryNext:
1545 pass
1572 pass
1546
1573
1547 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1574 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
1548 TERM = os.environ.get('TERM','dumb')
1575 TERM = os.environ.get('TERM','dumb')
1549 if TERM in ['dumb','emacs'] and os.name != 'nt':
1576 if TERM in ['dumb','emacs'] and os.name != 'nt':
1550 print strng
1577 print strng
1551 return
1578 return
1552 # chop off the topmost part of the string we don't want to see
1579 # chop off the topmost part of the string we don't want to see
1553 str_lines = strng.split(os.linesep)[start:]
1580 str_lines = strng.split(os.linesep)[start:]
1554 str_toprint = os.linesep.join(str_lines)
1581 str_toprint = os.linesep.join(str_lines)
1555 num_newlines = len(str_lines)
1582 num_newlines = len(str_lines)
1556 len_str = len(str_toprint)
1583 len_str = len(str_toprint)
1557
1584
1558 # Dumb heuristics to guesstimate number of on-screen lines the string
1585 # Dumb heuristics to guesstimate number of on-screen lines the string
1559 # takes. Very basic, but good enough for docstrings in reasonable
1586 # takes. Very basic, but good enough for docstrings in reasonable
1560 # terminals. If someone later feels like refining it, it's not hard.
1587 # terminals. If someone later feels like refining it, it's not hard.
1561 numlines = max(num_newlines,int(len_str/80)+1)
1588 numlines = max(num_newlines,int(len_str/80)+1)
1562
1589
1563 if os.name == "nt":
1590 if os.name == "nt":
1564 screen_lines_def = get_console_size(defaulty=25)[1]
1591 screen_lines_def = get_console_size(defaulty=25)[1]
1565 else:
1592 else:
1566 screen_lines_def = 25 # default value if we can't auto-determine
1593 screen_lines_def = 25 # default value if we can't auto-determine
1567
1594
1568 # auto-determine screen size
1595 # auto-determine screen size
1569 if screen_lines <= 0:
1596 if screen_lines <= 0:
1570 if TERM=='xterm':
1597 if TERM=='xterm':
1571 use_curses = USE_CURSES
1598 use_curses = USE_CURSES
1572 else:
1599 else:
1573 # curses causes problems on many terminals other than xterm.
1600 # curses causes problems on many terminals other than xterm.
1574 use_curses = False
1601 use_curses = False
1575 if use_curses:
1602 if use_curses:
1576 # There is a bug in curses, where *sometimes* it fails to properly
1603 # There is a bug in curses, where *sometimes* it fails to properly
1577 # initialize, and then after the endwin() call is made, the
1604 # initialize, and then after the endwin() call is made, the
1578 # terminal is left in an unusable state. Rather than trying to
1605 # terminal is left in an unusable state. Rather than trying to
1579 # check everytime for this (by requesting and comparing termios
1606 # check everytime for this (by requesting and comparing termios
1580 # flags each time), we just save the initial terminal state and
1607 # flags each time), we just save the initial terminal state and
1581 # unconditionally reset it every time. It's cheaper than making
1608 # unconditionally reset it every time. It's cheaper than making
1582 # the checks.
1609 # the checks.
1583 term_flags = termios.tcgetattr(sys.stdout)
1610 term_flags = termios.tcgetattr(sys.stdout)
1584 scr = curses.initscr()
1611 scr = curses.initscr()
1585 screen_lines_real,screen_cols = scr.getmaxyx()
1612 screen_lines_real,screen_cols = scr.getmaxyx()
1586 curses.endwin()
1613 curses.endwin()
1587 # Restore terminal state in case endwin() didn't.
1614 # Restore terminal state in case endwin() didn't.
1588 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1615 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
1589 # Now we have what we needed: the screen size in rows/columns
1616 # Now we have what we needed: the screen size in rows/columns
1590 screen_lines += screen_lines_real
1617 screen_lines += screen_lines_real
1591 #print '***Screen size:',screen_lines_real,'lines x',\
1618 #print '***Screen size:',screen_lines_real,'lines x',\
1592 #screen_cols,'columns.' # dbg
1619 #screen_cols,'columns.' # dbg
1593 else:
1620 else:
1594 screen_lines += screen_lines_def
1621 screen_lines += screen_lines_def
1595
1622
1596 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1623 #print 'numlines',numlines,'screenlines',screen_lines # dbg
1597 if numlines <= screen_lines :
1624 if numlines <= screen_lines :
1598 #print '*** normal print' # dbg
1625 #print '*** normal print' # dbg
1599 print >>Term.cout, str_toprint
1626 print >>Term.cout, str_toprint
1600 else:
1627 else:
1601 # Try to open pager and default to internal one if that fails.
1628 # Try to open pager and default to internal one if that fails.
1602 # All failure modes are tagged as 'retval=1', to match the return
1629 # All failure modes are tagged as 'retval=1', to match the return
1603 # value of a failed system command. If any intermediate attempt
1630 # value of a failed system command. If any intermediate attempt
1604 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1631 # sets retval to 1, at the end we resort to our own page_dumb() pager.
1605 pager_cmd = get_pager_cmd(pager_cmd)
1632 pager_cmd = get_pager_cmd(pager_cmd)
1606 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1633 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1607 if os.name == 'nt':
1634 if os.name == 'nt':
1608 if pager_cmd.startswith('type'):
1635 if pager_cmd.startswith('type'):
1609 # The default WinXP 'type' command is failing on complex strings.
1636 # The default WinXP 'type' command is failing on complex strings.
1610 retval = 1
1637 retval = 1
1611 else:
1638 else:
1612 tmpname = tempfile.mktemp('.txt')
1639 tmpname = tempfile.mktemp('.txt')
1613 tmpfile = file(tmpname,'wt')
1640 tmpfile = file(tmpname,'wt')
1614 tmpfile.write(strng)
1641 tmpfile.write(strng)
1615 tmpfile.close()
1642 tmpfile.close()
1616 cmd = "%s < %s" % (pager_cmd,tmpname)
1643 cmd = "%s < %s" % (pager_cmd,tmpname)
1617 if os.system(cmd):
1644 if os.system(cmd):
1618 retval = 1
1645 retval = 1
1619 else:
1646 else:
1620 retval = None
1647 retval = None
1621 os.remove(tmpname)
1648 os.remove(tmpname)
1622 else:
1649 else:
1623 try:
1650 try:
1624 retval = None
1651 retval = None
1625 # if I use popen4, things hang. No idea why.
1652 # if I use popen4, things hang. No idea why.
1626 #pager,shell_out = os.popen4(pager_cmd)
1653 #pager,shell_out = os.popen4(pager_cmd)
1627 pager = os.popen(pager_cmd,'w')
1654 pager = os.popen(pager_cmd,'w')
1628 pager.write(strng)
1655 pager.write(strng)
1629 pager.close()
1656 pager.close()
1630 retval = pager.close() # success returns None
1657 retval = pager.close() # success returns None
1631 except IOError,msg: # broken pipe when user quits
1658 except IOError,msg: # broken pipe when user quits
1632 if msg.args == (32,'Broken pipe'):
1659 if msg.args == (32,'Broken pipe'):
1633 retval = None
1660 retval = None
1634 else:
1661 else:
1635 retval = 1
1662 retval = 1
1636 except OSError:
1663 except OSError:
1637 # Other strange problems, sometimes seen in Win2k/cygwin
1664 # Other strange problems, sometimes seen in Win2k/cygwin
1638 retval = 1
1665 retval = 1
1639 if retval is not None:
1666 if retval is not None:
1640 page_dumb(strng,screen_lines=screen_lines)
1667 page_dumb(strng,screen_lines=screen_lines)
1641
1668
1642 #----------------------------------------------------------------------------
1669 #----------------------------------------------------------------------------
1643 def page_file(fname,start = 0, pager_cmd = None):
1670 def page_file(fname,start = 0, pager_cmd = None):
1644 """Page a file, using an optional pager command and starting line.
1671 """Page a file, using an optional pager command and starting line.
1645 """
1672 """
1646
1673
1647 pager_cmd = get_pager_cmd(pager_cmd)
1674 pager_cmd = get_pager_cmd(pager_cmd)
1648 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1675 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
1649
1676
1650 try:
1677 try:
1651 if os.environ['TERM'] in ['emacs','dumb']:
1678 if os.environ['TERM'] in ['emacs','dumb']:
1652 raise EnvironmentError
1679 raise EnvironmentError
1653 xsys(pager_cmd + ' ' + fname)
1680 xsys(pager_cmd + ' ' + fname)
1654 except:
1681 except:
1655 try:
1682 try:
1656 if start > 0:
1683 if start > 0:
1657 start -= 1
1684 start -= 1
1658 page(open(fname).read(),start)
1685 page(open(fname).read(),start)
1659 except:
1686 except:
1660 print 'Unable to show file',`fname`
1687 print 'Unable to show file',`fname`
1661
1688
1662
1689
1663 #----------------------------------------------------------------------------
1690 #----------------------------------------------------------------------------
1664 def snip_print(str,width = 75,print_full = 0,header = ''):
1691 def snip_print(str,width = 75,print_full = 0,header = ''):
1665 """Print a string snipping the midsection to fit in width.
1692 """Print a string snipping the midsection to fit in width.
1666
1693
1667 print_full: mode control:
1694 print_full: mode control:
1668 - 0: only snip long strings
1695 - 0: only snip long strings
1669 - 1: send to page() directly.
1696 - 1: send to page() directly.
1670 - 2: snip long strings and ask for full length viewing with page()
1697 - 2: snip long strings and ask for full length viewing with page()
1671 Return 1 if snipping was necessary, 0 otherwise."""
1698 Return 1 if snipping was necessary, 0 otherwise."""
1672
1699
1673 if print_full == 1:
1700 if print_full == 1:
1674 page(header+str)
1701 page(header+str)
1675 return 0
1702 return 0
1676
1703
1677 print header,
1704 print header,
1678 if len(str) < width:
1705 if len(str) < width:
1679 print str
1706 print str
1680 snip = 0
1707 snip = 0
1681 else:
1708 else:
1682 whalf = int((width -5)/2)
1709 whalf = int((width -5)/2)
1683 print str[:whalf] + ' <...> ' + str[-whalf:]
1710 print str[:whalf] + ' <...> ' + str[-whalf:]
1684 snip = 1
1711 snip = 1
1685 if snip and print_full == 2:
1712 if snip and print_full == 2:
1686 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1713 if raw_input(header+' Snipped. View (y/n)? [N]').lower() == 'y':
1687 page(str)
1714 page(str)
1688 return snip
1715 return snip
1689
1716
1690 #****************************************************************************
1717 #****************************************************************************
1691 # lists, dicts and structures
1718 # lists, dicts and structures
1692
1719
1693 def belong(candidates,checklist):
1720 def belong(candidates,checklist):
1694 """Check whether a list of items appear in a given list of options.
1721 """Check whether a list of items appear in a given list of options.
1695
1722
1696 Returns a list of 1 and 0, one for each candidate given."""
1723 Returns a list of 1 and 0, one for each candidate given."""
1697
1724
1698 return [x in checklist for x in candidates]
1725 return [x in checklist for x in candidates]
1699
1726
1700 #----------------------------------------------------------------------------
1727 #----------------------------------------------------------------------------
1701 def uniq_stable(elems):
1728 def uniq_stable(elems):
1702 """uniq_stable(elems) -> list
1729 """uniq_stable(elems) -> list
1703
1730
1704 Return from an iterable, a list of all the unique elements in the input,
1731 Return from an iterable, a list of all the unique elements in the input,
1705 but maintaining the order in which they first appear.
1732 but maintaining the order in which they first appear.
1706
1733
1707 A naive solution to this problem which just makes a dictionary with the
1734 A naive solution to this problem which just makes a dictionary with the
1708 elements as keys fails to respect the stability condition, since
1735 elements as keys fails to respect the stability condition, since
1709 dictionaries are unsorted by nature.
1736 dictionaries are unsorted by nature.
1710
1737
1711 Note: All elements in the input must be valid dictionary keys for this
1738 Note: All elements in the input must be valid dictionary keys for this
1712 routine to work, as it internally uses a dictionary for efficiency
1739 routine to work, as it internally uses a dictionary for efficiency
1713 reasons."""
1740 reasons."""
1714
1741
1715 unique = []
1742 unique = []
1716 unique_dict = {}
1743 unique_dict = {}
1717 for nn in elems:
1744 for nn in elems:
1718 if nn not in unique_dict:
1745 if nn not in unique_dict:
1719 unique.append(nn)
1746 unique.append(nn)
1720 unique_dict[nn] = None
1747 unique_dict[nn] = None
1721 return unique
1748 return unique
1722
1749
1723 #----------------------------------------------------------------------------
1750 #----------------------------------------------------------------------------
1724 class NLprinter:
1751 class NLprinter:
1725 """Print an arbitrarily nested list, indicating index numbers.
1752 """Print an arbitrarily nested list, indicating index numbers.
1726
1753
1727 An instance of this class called nlprint is available and callable as a
1754 An instance of this class called nlprint is available and callable as a
1728 function.
1755 function.
1729
1756
1730 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1757 nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent'
1731 and using 'sep' to separate the index from the value. """
1758 and using 'sep' to separate the index from the value. """
1732
1759
1733 def __init__(self):
1760 def __init__(self):
1734 self.depth = 0
1761 self.depth = 0
1735
1762
1736 def __call__(self,lst,pos='',**kw):
1763 def __call__(self,lst,pos='',**kw):
1737 """Prints the nested list numbering levels."""
1764 """Prints the nested list numbering levels."""
1738 kw.setdefault('indent',' ')
1765 kw.setdefault('indent',' ')
1739 kw.setdefault('sep',': ')
1766 kw.setdefault('sep',': ')
1740 kw.setdefault('start',0)
1767 kw.setdefault('start',0)
1741 kw.setdefault('stop',len(lst))
1768 kw.setdefault('stop',len(lst))
1742 # we need to remove start and stop from kw so they don't propagate
1769 # we need to remove start and stop from kw so they don't propagate
1743 # into a recursive call for a nested list.
1770 # into a recursive call for a nested list.
1744 start = kw['start']; del kw['start']
1771 start = kw['start']; del kw['start']
1745 stop = kw['stop']; del kw['stop']
1772 stop = kw['stop']; del kw['stop']
1746 if self.depth == 0 and 'header' in kw.keys():
1773 if self.depth == 0 and 'header' in kw.keys():
1747 print kw['header']
1774 print kw['header']
1748
1775
1749 for idx in range(start,stop):
1776 for idx in range(start,stop):
1750 elem = lst[idx]
1777 elem = lst[idx]
1751 if type(elem)==type([]):
1778 if type(elem)==type([]):
1752 self.depth += 1
1779 self.depth += 1
1753 self.__call__(elem,itpl('$pos$idx,'),**kw)
1780 self.__call__(elem,itpl('$pos$idx,'),**kw)
1754 self.depth -= 1
1781 self.depth -= 1
1755 else:
1782 else:
1756 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1783 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
1757
1784
1758 nlprint = NLprinter()
1785 nlprint = NLprinter()
1759 #----------------------------------------------------------------------------
1786 #----------------------------------------------------------------------------
1760 def all_belong(candidates,checklist):
1787 def all_belong(candidates,checklist):
1761 """Check whether a list of items ALL appear in a given list of options.
1788 """Check whether a list of items ALL appear in a given list of options.
1762
1789
1763 Returns a single 1 or 0 value."""
1790 Returns a single 1 or 0 value."""
1764
1791
1765 return 1-(0 in [x in checklist for x in candidates])
1792 return 1-(0 in [x in checklist for x in candidates])
1766
1793
1767 #----------------------------------------------------------------------------
1794 #----------------------------------------------------------------------------
1768 def sort_compare(lst1,lst2,inplace = 1):
1795 def sort_compare(lst1,lst2,inplace = 1):
1769 """Sort and compare two lists.
1796 """Sort and compare two lists.
1770
1797
1771 By default it does it in place, thus modifying the lists. Use inplace = 0
1798 By default it does it in place, thus modifying the lists. Use inplace = 0
1772 to avoid that (at the cost of temporary copy creation)."""
1799 to avoid that (at the cost of temporary copy creation)."""
1773 if not inplace:
1800 if not inplace:
1774 lst1 = lst1[:]
1801 lst1 = lst1[:]
1775 lst2 = lst2[:]
1802 lst2 = lst2[:]
1776 lst1.sort(); lst2.sort()
1803 lst1.sort(); lst2.sort()
1777 return lst1 == lst2
1804 return lst1 == lst2
1778
1805
1779 #----------------------------------------------------------------------------
1806 #----------------------------------------------------------------------------
1780 def mkdict(**kwargs):
1807 def mkdict(**kwargs):
1781 """Return a dict from a keyword list.
1808 """Return a dict from a keyword list.
1782
1809
1783 It's just syntactic sugar for making ditcionary creation more convenient:
1810 It's just syntactic sugar for making ditcionary creation more convenient:
1784 # the standard way
1811 # the standard way
1785 >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 }
1812 >>>data = { 'red' : 1, 'green' : 2, 'blue' : 3 }
1786 # a cleaner way
1813 # a cleaner way
1787 >>>data = dict(red=1, green=2, blue=3)
1814 >>>data = dict(red=1, green=2, blue=3)
1788
1815
1789 If you need more than this, look at the Struct() class."""
1816 If you need more than this, look at the Struct() class."""
1790
1817
1791 return kwargs
1818 return kwargs
1792
1819
1793 #----------------------------------------------------------------------------
1820 #----------------------------------------------------------------------------
1794 def list2dict(lst):
1821 def list2dict(lst):
1795 """Takes a list of (key,value) pairs and turns it into a dict."""
1822 """Takes a list of (key,value) pairs and turns it into a dict."""
1796
1823
1797 dic = {}
1824 dic = {}
1798 for k,v in lst: dic[k] = v
1825 for k,v in lst: dic[k] = v
1799 return dic
1826 return dic
1800
1827
1801 #----------------------------------------------------------------------------
1828 #----------------------------------------------------------------------------
1802 def list2dict2(lst,default=''):
1829 def list2dict2(lst,default=''):
1803 """Takes a list and turns it into a dict.
1830 """Takes a list and turns it into a dict.
1804 Much slower than list2dict, but more versatile. This version can take
1831 Much slower than list2dict, but more versatile. This version can take
1805 lists with sublists of arbitrary length (including sclars)."""
1832 lists with sublists of arbitrary length (including sclars)."""
1806
1833
1807 dic = {}
1834 dic = {}
1808 for elem in lst:
1835 for elem in lst:
1809 if type(elem) in (types.ListType,types.TupleType):
1836 if type(elem) in (types.ListType,types.TupleType):
1810 size = len(elem)
1837 size = len(elem)
1811 if size == 0:
1838 if size == 0:
1812 pass
1839 pass
1813 elif size == 1:
1840 elif size == 1:
1814 dic[elem] = default
1841 dic[elem] = default
1815 else:
1842 else:
1816 k,v = elem[0], elem[1:]
1843 k,v = elem[0], elem[1:]
1817 if len(v) == 1: v = v[0]
1844 if len(v) == 1: v = v[0]
1818 dic[k] = v
1845 dic[k] = v
1819 else:
1846 else:
1820 dic[elem] = default
1847 dic[elem] = default
1821 return dic
1848 return dic
1822
1849
1823 #----------------------------------------------------------------------------
1850 #----------------------------------------------------------------------------
1824 def flatten(seq):
1851 def flatten(seq):
1825 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1852 """Flatten a list of lists (NOT recursive, only works for 2d lists)."""
1826
1853
1827 return [x for subseq in seq for x in subseq]
1854 return [x for subseq in seq for x in subseq]
1828
1855
1829 #----------------------------------------------------------------------------
1856 #----------------------------------------------------------------------------
1830 def get_slice(seq,start=0,stop=None,step=1):
1857 def get_slice(seq,start=0,stop=None,step=1):
1831 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1858 """Get a slice of a sequence with variable step. Specify start,stop,step."""
1832 if stop == None:
1859 if stop == None:
1833 stop = len(seq)
1860 stop = len(seq)
1834 item = lambda i: seq[i]
1861 item = lambda i: seq[i]
1835 return map(item,xrange(start,stop,step))
1862 return map(item,xrange(start,stop,step))
1836
1863
1837 #----------------------------------------------------------------------------
1864 #----------------------------------------------------------------------------
1838 def chop(seq,size):
1865 def chop(seq,size):
1839 """Chop a sequence into chunks of the given size."""
1866 """Chop a sequence into chunks of the given size."""
1840 chunk = lambda i: seq[i:i+size]
1867 chunk = lambda i: seq[i:i+size]
1841 return map(chunk,xrange(0,len(seq),size))
1868 return map(chunk,xrange(0,len(seq),size))
1842
1869
1843 #----------------------------------------------------------------------------
1870 #----------------------------------------------------------------------------
1844 # with is a keyword as of python 2.5, so this function is renamed to withobj
1871 # with is a keyword as of python 2.5, so this function is renamed to withobj
1845 # from its old 'with' name.
1872 # from its old 'with' name.
1846 def with_obj(object, **args):
1873 def with_obj(object, **args):
1847 """Set multiple attributes for an object, similar to Pascal's with.
1874 """Set multiple attributes for an object, similar to Pascal's with.
1848
1875
1849 Example:
1876 Example:
1850 with_obj(jim,
1877 with_obj(jim,
1851 born = 1960,
1878 born = 1960,
1852 haircolour = 'Brown',
1879 haircolour = 'Brown',
1853 eyecolour = 'Green')
1880 eyecolour = 'Green')
1854
1881
1855 Credit: Greg Ewing, in
1882 Credit: Greg Ewing, in
1856 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1883 http://mail.python.org/pipermail/python-list/2001-May/040703.html.
1857
1884
1858 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1885 NOTE: up until IPython 0.7.2, this was called simply 'with', but 'with'
1859 has become a keyword for Python 2.5, so we had to rename it."""
1886 has become a keyword for Python 2.5, so we had to rename it."""
1860
1887
1861 object.__dict__.update(args)
1888 object.__dict__.update(args)
1862
1889
1863 #----------------------------------------------------------------------------
1890 #----------------------------------------------------------------------------
1864 def setattr_list(obj,alist,nspace = None):
1891 def setattr_list(obj,alist,nspace = None):
1865 """Set a list of attributes for an object taken from a namespace.
1892 """Set a list of attributes for an object taken from a namespace.
1866
1893
1867 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1894 setattr_list(obj,alist,nspace) -> sets in obj all the attributes listed in
1868 alist with their values taken from nspace, which must be a dict (something
1895 alist with their values taken from nspace, which must be a dict (something
1869 like locals() will often do) If nspace isn't given, locals() of the
1896 like locals() will often do) If nspace isn't given, locals() of the
1870 *caller* is used, so in most cases you can omit it.
1897 *caller* is used, so in most cases you can omit it.
1871
1898
1872 Note that alist can be given as a string, which will be automatically
1899 Note that alist can be given as a string, which will be automatically
1873 split into a list on whitespace. If given as a list, it must be a list of
1900 split into a list on whitespace. If given as a list, it must be a list of
1874 *strings* (the variable names themselves), not of variables."""
1901 *strings* (the variable names themselves), not of variables."""
1875
1902
1876 # this grabs the local variables from the *previous* call frame -- that is
1903 # this grabs the local variables from the *previous* call frame -- that is
1877 # the locals from the function that called setattr_list().
1904 # the locals from the function that called setattr_list().
1878 # - snipped from weave.inline()
1905 # - snipped from weave.inline()
1879 if nspace is None:
1906 if nspace is None:
1880 call_frame = sys._getframe().f_back
1907 call_frame = sys._getframe().f_back
1881 nspace = call_frame.f_locals
1908 nspace = call_frame.f_locals
1882
1909
1883 if type(alist) in StringTypes:
1910 if type(alist) in StringTypes:
1884 alist = alist.split()
1911 alist = alist.split()
1885 for attr in alist:
1912 for attr in alist:
1886 val = eval(attr,nspace)
1913 val = eval(attr,nspace)
1887 setattr(obj,attr,val)
1914 setattr(obj,attr,val)
1888
1915
1889 #----------------------------------------------------------------------------
1916 #----------------------------------------------------------------------------
1890 def getattr_list(obj,alist,*args):
1917 def getattr_list(obj,alist,*args):
1891 """getattr_list(obj,alist[, default]) -> attribute list.
1918 """getattr_list(obj,alist[, default]) -> attribute list.
1892
1919
1893 Get a list of named attributes for an object. When a default argument is
1920 Get a list of named attributes for an object. When a default argument is
1894 given, it is returned when the attribute doesn't exist; without it, an
1921 given, it is returned when the attribute doesn't exist; without it, an
1895 exception is raised in that case.
1922 exception is raised in that case.
1896
1923
1897 Note that alist can be given as a string, which will be automatically
1924 Note that alist can be given as a string, which will be automatically
1898 split into a list on whitespace. If given as a list, it must be a list of
1925 split into a list on whitespace. If given as a list, it must be a list of
1899 *strings* (the variable names themselves), not of variables."""
1926 *strings* (the variable names themselves), not of variables."""
1900
1927
1901 if type(alist) in StringTypes:
1928 if type(alist) in StringTypes:
1902 alist = alist.split()
1929 alist = alist.split()
1903 if args:
1930 if args:
1904 if len(args)==1:
1931 if len(args)==1:
1905 default = args[0]
1932 default = args[0]
1906 return map(lambda attr: getattr(obj,attr,default),alist)
1933 return map(lambda attr: getattr(obj,attr,default),alist)
1907 else:
1934 else:
1908 raise ValueError,'getattr_list() takes only one optional argument'
1935 raise ValueError,'getattr_list() takes only one optional argument'
1909 else:
1936 else:
1910 return map(lambda attr: getattr(obj,attr),alist)
1937 return map(lambda attr: getattr(obj,attr),alist)
1911
1938
1912 #----------------------------------------------------------------------------
1939 #----------------------------------------------------------------------------
1913 def map_method(method,object_list,*argseq,**kw):
1940 def map_method(method,object_list,*argseq,**kw):
1914 """map_method(method,object_list,*args,**kw) -> list
1941 """map_method(method,object_list,*args,**kw) -> list
1915
1942
1916 Return a list of the results of applying the methods to the items of the
1943 Return a list of the results of applying the methods to the items of the
1917 argument sequence(s). If more than one sequence is given, the method is
1944 argument sequence(s). If more than one sequence is given, the method is
1918 called with an argument list consisting of the corresponding item of each
1945 called with an argument list consisting of the corresponding item of each
1919 sequence. All sequences must be of the same length.
1946 sequence. All sequences must be of the same length.
1920
1947
1921 Keyword arguments are passed verbatim to all objects called.
1948 Keyword arguments are passed verbatim to all objects called.
1922
1949
1923 This is Python code, so it's not nearly as fast as the builtin map()."""
1950 This is Python code, so it's not nearly as fast as the builtin map()."""
1924
1951
1925 out_list = []
1952 out_list = []
1926 idx = 0
1953 idx = 0
1927 for object in object_list:
1954 for object in object_list:
1928 try:
1955 try:
1929 handler = getattr(object, method)
1956 handler = getattr(object, method)
1930 except AttributeError:
1957 except AttributeError:
1931 out_list.append(None)
1958 out_list.append(None)
1932 else:
1959 else:
1933 if argseq:
1960 if argseq:
1934 args = map(lambda lst:lst[idx],argseq)
1961 args = map(lambda lst:lst[idx],argseq)
1935 #print 'ob',object,'hand',handler,'ar',args # dbg
1962 #print 'ob',object,'hand',handler,'ar',args # dbg
1936 out_list.append(handler(args,**kw))
1963 out_list.append(handler(args,**kw))
1937 else:
1964 else:
1938 out_list.append(handler(**kw))
1965 out_list.append(handler(**kw))
1939 idx += 1
1966 idx += 1
1940 return out_list
1967 return out_list
1941
1968
1942 #----------------------------------------------------------------------------
1969 #----------------------------------------------------------------------------
1943 def get_class_members(cls):
1970 def get_class_members(cls):
1944 ret = dir(cls)
1971 ret = dir(cls)
1945 if hasattr(cls,'__bases__'):
1972 if hasattr(cls,'__bases__'):
1946 for base in cls.__bases__:
1973 for base in cls.__bases__:
1947 ret.extend(get_class_members(base))
1974 ret.extend(get_class_members(base))
1948 return ret
1975 return ret
1949
1976
1950 #----------------------------------------------------------------------------
1977 #----------------------------------------------------------------------------
1951 def dir2(obj):
1978 def dir2(obj):
1952 """dir2(obj) -> list of strings
1979 """dir2(obj) -> list of strings
1953
1980
1954 Extended version of the Python builtin dir(), which does a few extra
1981 Extended version of the Python builtin dir(), which does a few extra
1955 checks, and supports common objects with unusual internals that confuse
1982 checks, and supports common objects with unusual internals that confuse
1956 dir(), such as Traits and PyCrust.
1983 dir(), such as Traits and PyCrust.
1957
1984
1958 This version is guaranteed to return only a list of true strings, whereas
1985 This version is guaranteed to return only a list of true strings, whereas
1959 dir() returns anything that objects inject into themselves, even if they
1986 dir() returns anything that objects inject into themselves, even if they
1960 are later not really valid for attribute access (many extension libraries
1987 are later not really valid for attribute access (many extension libraries
1961 have such bugs).
1988 have such bugs).
1962 """
1989 """
1963
1990
1964 # Start building the attribute list via dir(), and then complete it
1991 # Start building the attribute list via dir(), and then complete it
1965 # with a few extra special-purpose calls.
1992 # with a few extra special-purpose calls.
1966 words = dir(obj)
1993 words = dir(obj)
1967
1994
1968 if hasattr(obj,'__class__'):
1995 if hasattr(obj,'__class__'):
1969 words.append('__class__')
1996 words.append('__class__')
1970 words.extend(get_class_members(obj.__class__))
1997 words.extend(get_class_members(obj.__class__))
1971 #if '__base__' in words: 1/0
1998 #if '__base__' in words: 1/0
1972
1999
1973 # Some libraries (such as traits) may introduce duplicates, we want to
2000 # Some libraries (such as traits) may introduce duplicates, we want to
1974 # track and clean this up if it happens
2001 # track and clean this up if it happens
1975 may_have_dupes = False
2002 may_have_dupes = False
1976
2003
1977 # this is the 'dir' function for objects with Enthought's traits
2004 # this is the 'dir' function for objects with Enthought's traits
1978 if hasattr(obj, 'trait_names'):
2005 if hasattr(obj, 'trait_names'):
1979 try:
2006 try:
1980 words.extend(obj.trait_names())
2007 words.extend(obj.trait_names())
1981 may_have_dupes = True
2008 may_have_dupes = True
1982 except TypeError:
2009 except TypeError:
1983 # This will happen if `obj` is a class and not an instance.
2010 # This will happen if `obj` is a class and not an instance.
1984 pass
2011 pass
1985
2012
1986 # Support for PyCrust-style _getAttributeNames magic method.
2013 # Support for PyCrust-style _getAttributeNames magic method.
1987 if hasattr(obj, '_getAttributeNames'):
2014 if hasattr(obj, '_getAttributeNames'):
1988 try:
2015 try:
1989 words.extend(obj._getAttributeNames())
2016 words.extend(obj._getAttributeNames())
1990 may_have_dupes = True
2017 may_have_dupes = True
1991 except TypeError:
2018 except TypeError:
1992 # `obj` is a class and not an instance. Ignore
2019 # `obj` is a class and not an instance. Ignore
1993 # this error.
2020 # this error.
1994 pass
2021 pass
1995
2022
1996 if may_have_dupes:
2023 if may_have_dupes:
1997 # eliminate possible duplicates, as some traits may also
2024 # eliminate possible duplicates, as some traits may also
1998 # appear as normal attributes in the dir() call.
2025 # appear as normal attributes in the dir() call.
1999 words = list(set(words))
2026 words = list(set(words))
2000 words.sort()
2027 words.sort()
2001
2028
2002 # filter out non-string attributes which may be stuffed by dir() calls
2029 # filter out non-string attributes which may be stuffed by dir() calls
2003 # and poor coding in third-party modules
2030 # and poor coding in third-party modules
2004 return [w for w in words if isinstance(w, basestring)]
2031 return [w for w in words if isinstance(w, basestring)]
2005
2032
2006 #----------------------------------------------------------------------------
2033 #----------------------------------------------------------------------------
2007 def import_fail_info(mod_name,fns=None):
2034 def import_fail_info(mod_name,fns=None):
2008 """Inform load failure for a module."""
2035 """Inform load failure for a module."""
2009
2036
2010 if fns == None:
2037 if fns == None:
2011 warn("Loading of %s failed.\n" % (mod_name,))
2038 warn("Loading of %s failed.\n" % (mod_name,))
2012 else:
2039 else:
2013 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2040 warn("Loading of %s from %s failed.\n" % (fns,mod_name))
2014
2041
2015 #----------------------------------------------------------------------------
2042 #----------------------------------------------------------------------------
2016 # Proposed popitem() extension, written as a method
2043 # Proposed popitem() extension, written as a method
2017
2044
2018
2045
2019 class NotGiven: pass
2046 class NotGiven: pass
2020
2047
2021 def popkey(dct,key,default=NotGiven):
2048 def popkey(dct,key,default=NotGiven):
2022 """Return dct[key] and delete dct[key].
2049 """Return dct[key] and delete dct[key].
2023
2050
2024 If default is given, return it if dct[key] doesn't exist, otherwise raise
2051 If default is given, return it if dct[key] doesn't exist, otherwise raise
2025 KeyError. """
2052 KeyError. """
2026
2053
2027 try:
2054 try:
2028 val = dct[key]
2055 val = dct[key]
2029 except KeyError:
2056 except KeyError:
2030 if default is NotGiven:
2057 if default is NotGiven:
2031 raise
2058 raise
2032 else:
2059 else:
2033 return default
2060 return default
2034 else:
2061 else:
2035 del dct[key]
2062 del dct[key]
2036 return val
2063 return val
2037
2064
2038 def wrap_deprecated(func, suggest = '<nothing>'):
2065 def wrap_deprecated(func, suggest = '<nothing>'):
2039 def newFunc(*args, **kwargs):
2066 def newFunc(*args, **kwargs):
2040 warnings.warn("Call to deprecated function %s, use %s instead" %
2067 warnings.warn("Call to deprecated function %s, use %s instead" %
2041 ( func.__name__, suggest),
2068 ( func.__name__, suggest),
2042 category=DeprecationWarning,
2069 category=DeprecationWarning,
2043 stacklevel = 2)
2070 stacklevel = 2)
2044 return func(*args, **kwargs)
2071 return func(*args, **kwargs)
2045 return newFunc
2072 return newFunc
2046
2073
2047
2074
2048 def _num_cpus_unix():
2075 def _num_cpus_unix():
2049 """Return the number of active CPUs on a Unix system."""
2076 """Return the number of active CPUs on a Unix system."""
2050 return os.sysconf("SC_NPROCESSORS_ONLN")
2077 return os.sysconf("SC_NPROCESSORS_ONLN")
2051
2078
2052
2079
2053 def _num_cpus_darwin():
2080 def _num_cpus_darwin():
2054 """Return the number of active CPUs on a Darwin system."""
2081 """Return the number of active CPUs on a Darwin system."""
2055 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2082 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2056 return p.stdout.read()
2083 return p.stdout.read()
2057
2084
2058
2085
2059 def _num_cpus_windows():
2086 def _num_cpus_windows():
2060 """Return the number of active CPUs on a Windows system."""
2087 """Return the number of active CPUs on a Windows system."""
2061 return os.environ.get("NUMBER_OF_PROCESSORS")
2088 return os.environ.get("NUMBER_OF_PROCESSORS")
2062
2089
2063
2090
2064 def num_cpus():
2091 def num_cpus():
2065 """Return the effective number of CPUs in the system as an integer.
2092 """Return the effective number of CPUs in the system as an integer.
2066
2093
2067 This cross-platform function makes an attempt at finding the total number of
2094 This cross-platform function makes an attempt at finding the total number of
2068 available CPUs in the system, as returned by various underlying system and
2095 available CPUs in the system, as returned by various underlying system and
2069 python calls.
2096 python calls.
2070
2097
2071 If it can't find a sensible answer, it returns 1 (though an error *may* make
2098 If it can't find a sensible answer, it returns 1 (though an error *may* make
2072 it return a large positive number that's actually incorrect).
2099 it return a large positive number that's actually incorrect).
2073 """
2100 """
2074
2101
2075 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2102 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2076 # for the names of the keys we needed to look up for this function. This
2103 # for the names of the keys we needed to look up for this function. This
2077 # code was inspired by their equivalent function.
2104 # code was inspired by their equivalent function.
2078
2105
2079 ncpufuncs = {'Linux':_num_cpus_unix,
2106 ncpufuncs = {'Linux':_num_cpus_unix,
2080 'Darwin':_num_cpus_darwin,
2107 'Darwin':_num_cpus_darwin,
2081 'Windows':_num_cpus_windows,
2108 'Windows':_num_cpus_windows,
2082 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2109 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2083 # See http://bugs.python.org/issue1082 for details.
2110 # See http://bugs.python.org/issue1082 for details.
2084 'Microsoft':_num_cpus_windows,
2111 'Microsoft':_num_cpus_windows,
2085 }
2112 }
2086
2113
2087 ncpufunc = ncpufuncs.get(platform.system(),
2114 ncpufunc = ncpufuncs.get(platform.system(),
2088 # default to unix version (Solaris, AIX, etc)
2115 # default to unix version (Solaris, AIX, etc)
2089 _num_cpus_unix)
2116 _num_cpus_unix)
2090
2117
2091 try:
2118 try:
2092 ncpus = max(1,int(ncpufunc()))
2119 ncpus = max(1,int(ncpufunc()))
2093 except:
2120 except:
2094 ncpus = 1
2121 ncpus = 1
2095 return ncpus
2122 return ncpus
2096
2123
2097 #*************************** end of file <genutils.py> **********************
2124 #*************************** end of file <genutils.py> **********************
@@ -1,323 +1,324 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.config import cutils
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((%r,%s))" % \
171 print "mec = client.MultiEngineClient()"
172 (control_host,control_port)
173 print
172 print
174 print 'You can then cleanly stop the cluster from IPython using:'
173 print 'You can then cleanly stop the cluster from IPython using:'
175 print
174 print
176 print 'mec.kill(controller=True)'
175 print 'mec.kill(controller=True)'
177 print
176 print
178
177
179
178
180 def clusterLocal(opt,arg):
179 def clusterLocal(opt,arg):
181 """Start a cluster on the local machine."""
180 """Start a cluster on the local machine."""
182
181
183 # Store all logs inside the ipython directory
182 # Store all logs inside the ipython directory
184 ipdir = cutils.get_ipython_dir()
183 ipdir = cutils.get_ipython_dir()
185 pjoin = os.path.join
184 pjoin = os.path.join
186
185
187 logfile = opt.logfile
186 logfile = opt.logfile
188 if logfile is None:
187 if logfile is None:
189 logdir_base = pjoin(ipdir,'log')
188 logdir_base = pjoin(ipdir,'log')
190 ensureDir(logdir_base)
189 ensureDir(logdir_base)
191 logfile = pjoin(logdir_base,'ipcluster-')
190 logfile = pjoin(logdir_base,'ipcluster-')
192
191
193 print 'Starting controller:',
192 print 'Starting controller:',
194 controller = Popen(['ipcontroller','--logfile',logfile])
193 controller = Popen(['ipcontroller','--logfile',logfile,'-x','-y'])
195 print 'Controller PID:',controller.pid
194 print 'Controller PID:',controller.pid
196
195
197 print 'Starting engines: ',
196 print 'Starting engines: ',
198 time.sleep(3)
197 time.sleep(5)
199
198
200 englogfile = '%s%s-' % (logfile,controller.pid)
199 englogfile = '%s%s-' % (logfile,controller.pid)
201 mpi = opt.mpi
200 mpi = opt.mpi
202 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
203 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi, '--logfile',englogfile])]
202 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi',
203 mpi, '--logfile',englogfile])]
204 # engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi])]
204 else: # do what we would normally do
205 else: # do what we would normally do
205 engines = [ Popen(['ipengine','--logfile',englogfile])
206 engines = [ Popen(['ipengine','--logfile',englogfile])
206 for i in range(opt.n) ]
207 for i in range(opt.n) ]
207 eids = [e.pid for e in engines]
208 eids = [e.pid for e in engines]
208 print 'Engines PIDs: ',eids
209 print 'Engines PIDs: ',eids
209 print 'Log files: %s*' % englogfile
210 print 'Log files: %s*' % englogfile
210
211
211 proc_ids = eids + [controller.pid]
212 proc_ids = eids + [controller.pid]
212 procs = engines + [controller]
213 procs = engines + [controller]
213
214
214 grpid = os.getpgrp()
215 grpid = os.getpgrp()
215 try:
216 try:
216 startMsg('127.0.0.1')
217 startMsg('127.0.0.1')
217 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:'
218 print
219 print
219 print 'kill -INT',grpid
220 print 'kill -INT',grpid
220 print
221 print
221 try:
222 try:
222 while True:
223 while True:
223 time.sleep(5)
224 time.sleep(5)
224 except:
225 except:
225 pass
226 pass
226 finally:
227 finally:
227 print 'Stopping cluster. Cleaning up...'
228 print 'Stopping cluster. Cleaning up...'
228 cleanup(stop,controller,engines)
229 cleanup(stop,controller,engines)
229 for i in range(4):
230 for i in range(4):
230 time.sleep(i+2)
231 time.sleep(i+2)
231 nZombies = numAlive(controller,engines)
232 nZombies = numAlive(controller,engines)
232 if nZombies== 0:
233 if nZombies== 0:
233 print 'OK: All processes cleaned up.'
234 print 'OK: All processes cleaned up.'
234 break
235 break
235 print 'Trying again, %d processes did not stop...' % nZombies
236 print 'Trying again, %d processes did not stop...' % nZombies
236 cleanup(kill,controller,engines)
237 cleanup(kill,controller,engines)
237 if numAlive(controller,engines) == 0:
238 if numAlive(controller,engines) == 0:
238 print 'OK: All processes cleaned up.'
239 print 'OK: All processes cleaned up.'
239 break
240 break
240 else:
241 else:
241 print '*'*75
242 print '*'*75
242 print 'ERROR: could not kill some processes, try to do it',
243 print 'ERROR: could not kill some processes, try to do it',
243 print 'manually.'
244 print 'manually.'
244 zombies = []
245 zombies = []
245 if controller.returncode is None:
246 if controller.returncode is None:
246 print 'Controller is alive: pid =',controller.pid
247 print 'Controller is alive: pid =',controller.pid
247 zombies.append(controller.pid)
248 zombies.append(controller.pid)
248 liveEngines = [ e for e in engines if e.returncode is None ]
249 liveEngines = [ e for e in engines if e.returncode is None ]
249 for e in liveEngines:
250 for e in liveEngines:
250 print 'Engine is alive: pid =',e.pid
251 print 'Engine is alive: pid =',e.pid
251 zombies.append(e.pid)
252 zombies.append(e.pid)
252 print
253 print
253 print 'Zombie summary:',' '.join(map(str,zombies))
254 print 'Zombie summary:',' '.join(map(str,zombies))
254
255
255 def clusterRemote(opt,arg):
256 def clusterRemote(opt,arg):
256 """Start a remote cluster over SSH"""
257 """Start a remote cluster over SSH"""
257
258
258 # Load the remote cluster configuration
259 # Load the remote cluster configuration
259 clConfig = {}
260 clConfig = {}
260 execfile(opt.clusterfile,clConfig)
261 execfile(opt.clusterfile,clConfig)
261 contConfig = clConfig['controller']
262 contConfig = clConfig['controller']
262 engConfig = clConfig['engines']
263 engConfig = clConfig['engines']
263 # Determine where to find sshx:
264 # Determine where to find sshx:
264 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
265 sshx = clConfig.get('sshx',os.environ.get('IPYTHON_SSHX','sshx'))
265
266
266 # Store all logs inside the ipython directory
267 # Store all logs inside the ipython directory
267 ipdir = cutils.get_ipython_dir()
268 ipdir = cutils.get_ipython_dir()
268 pjoin = os.path.join
269 pjoin = os.path.join
269
270
270 logfile = opt.logfile
271 logfile = opt.logfile
271 if logfile is None:
272 if logfile is None:
272 logdir_base = pjoin(ipdir,'log')
273 logdir_base = pjoin(ipdir,'log')
273 ensureDir(logdir_base)
274 ensureDir(logdir_base)
274 logfile = pjoin(logdir_base,'ipcluster')
275 logfile = pjoin(logdir_base,'ipcluster')
275
276
276 # Append this script's PID to the logfile name always
277 # Append this script's PID to the logfile name always
277 logfile = '%s-%s' % (logfile,os.getpid())
278 logfile = '%s-%s' % (logfile,os.getpid())
278
279
279 print 'Starting controller:'
280 print 'Starting controller:'
280 # Controller data:
281 # Controller data:
281 xsys = os.system
282 xsys = os.system
282
283
283 contHost = contConfig['host']
284 contHost = contConfig['host']
284 contLog = '%s-con-%s-' % (logfile,contHost)
285 contLog = '%s-con-%s-' % (logfile,contHost)
285 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
286 cmd = "ssh %s '%s' 'ipcontroller --logfile %s' &" % \
286 (contHost,sshx,contLog)
287 (contHost,sshx,contLog)
287 #print 'cmd:<%s>' % cmd # dbg
288 #print 'cmd:<%s>' % cmd # dbg
288 xsys(cmd)
289 xsys(cmd)
289 time.sleep(2)
290 time.sleep(2)
290
291
291 print 'Starting engines: '
292 print 'Starting engines: '
292 for engineHost,engineData in engConfig.iteritems():
293 for engineHost,engineData in engConfig.iteritems():
293 if isinstance(engineData,int):
294 if isinstance(engineData,int):
294 numEngines = engineData
295 numEngines = engineData
295 else:
296 else:
296 raise NotImplementedError('port configuration not finished for engines')
297 raise NotImplementedError('port configuration not finished for engines')
297
298
298 print 'Sarting %d engines on %s' % (numEngines,engineHost)
299 print 'Sarting %d engines on %s' % (numEngines,engineHost)
299 engLog = '%s-eng-%s-' % (logfile,engineHost)
300 engLog = '%s-eng-%s-' % (logfile,engineHost)
300 for i in range(numEngines):
301 for i in range(numEngines):
301 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
302 cmd = "ssh %s '%s' 'ipengine --controller-ip %s --logfile %s' &" % \
302 (engineHost,sshx,contHost,engLog)
303 (engineHost,sshx,contHost,engLog)
303 #print 'cmd:<%s>' % cmd # dbg
304 #print 'cmd:<%s>' % cmd # dbg
304 xsys(cmd)
305 xsys(cmd)
305 # Wait after each host a little bit
306 # Wait after each host a little bit
306 time.sleep(1)
307 time.sleep(1)
307
308
308 startMsg(contConfig['host'])
309 startMsg(contConfig['host'])
309
310
310 def main():
311 def main():
311 """Main driver for the two big options: local or remote cluster."""
312 """Main driver for the two big options: local or remote cluster."""
312
313
313 opt,arg = parse_args()
314 opt,arg = parse_args()
314
315
315 clusterfile = opt.clusterfile
316 clusterfile = opt.clusterfile
316 if clusterfile:
317 if clusterfile:
317 clusterRemote(opt,arg)
318 clusterRemote(opt,arg)
318 else:
319 else:
319 clusterLocal(opt,arg)
320 clusterLocal(opt,arg)
320
321
321
322
322 if __name__=='__main__':
323 if __name__=='__main__':
323 main()
324 main()
@@ -1,169 +1,171 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 # 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
62 mpikey = kernel_config['mpi']['default']
64 mpikey = kernel_config['mpi']['default']
63 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
65 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
64 if mpi_import_statement is not None:
66 if mpi_import_statement is not None:
65 try:
67 try:
66 exec mpi_import_statement in locals(), globals()
68 exec mpi_import_statement in globals()
67 except:
69 except:
68 mpi = None
70 mpi = None
69 else:
71 else:
70 mpi = None
72 mpi = None
71
73
72 # Start logging
74 # Start logging
73 logfile = kernel_config['engine']['logfile']
75 logfile = kernel_config['engine']['logfile']
74 if logfile:
76 if logfile:
75 logfile = logfile + str(os.getpid()) + '.log'
77 logfile = logfile + str(os.getpid()) + '.log'
76 try:
78 try:
77 openLogFile = open(logfile, 'w')
79 openLogFile = open(logfile, 'w')
78 except:
80 except:
79 openLogFile = sys.stdout
81 openLogFile = sys.stdout
80 else:
82 else:
81 openLogFile = sys.stdout
83 openLogFile = sys.stdout
82 log.startLogging(openLogFile)
84 log.startLogging(openLogFile)
83
85
84 # Create the underlying shell class and EngineService
86 # Create the underlying shell class and EngineService
85 shell_class = import_item(core_config['shell']['shell_class'])
87 shell_class = import_item(core_config['shell']['shell_class'])
86 engine_service = EngineService(shell_class, mpi=mpi)
88 engine_service = EngineService(shell_class, mpi=mpi)
87 shell_import_statement = core_config['shell']['import_statement']
89 shell_import_statement = core_config['shell']['import_statement']
88 if shell_import_statement:
90 if shell_import_statement:
89 try:
91 try:
90 engine_service.execute(shell_import_statement)
92 engine_service.execute(shell_import_statement)
91 except:
93 except:
92 log.msg("Error running import_statement: %s" % sis)
94 log.msg("Error running import_statement: %s" % sis)
93
95
94 # Create the service hierarchy
96 # Create the service hierarchy
95 main_service = service.MultiService()
97 main_service = service.MultiService()
96 engine_service.setServiceParent(main_service)
98 engine_service.setServiceParent(main_service)
97 tub_service = Tub()
99 tub_service = Tub()
98 tub_service.setServiceParent(main_service)
100 tub_service.setServiceParent(main_service)
99 # This needs to be called before the connection is initiated
101 # This needs to be called before the connection is initiated
100 main_service.startService()
102 main_service.startService()
101
103
102 # This initiates the connection to the controller and calls
104 # This initiates the connection to the controller and calls
103 # 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
104 engine_connector = EngineConnector(tub_service)
106 engine_connector = EngineConnector(tub_service)
105 furl_file = kernel_config['engine']['furl_file']
107 furl_file = kernel_config['engine']['furl_file']
106 d = engine_connector.connect_to_controller(engine_service, furl_file)
108 d = engine_connector.connect_to_controller(engine_service, furl_file)
107 d.addErrback(lambda _: reactor.stop())
109 d.addErrback(lambda _: reactor.stop())
108
110
109 reactor.run()
111 reactor.run()
110
112
111
113
112 def init_config():
114 def init_config():
113 """
115 """
114 Initialize the configuration using default and command line options.
116 Initialize the configuration using default and command line options.
115 """
117 """
116
118
117 parser = OptionParser()
119 parser = OptionParser()
118
120
119 parser.add_option(
121 parser.add_option(
120 "--furl-file",
122 "--furl-file",
121 type="string",
123 type="string",
122 dest="furl_file",
124 dest="furl_file",
123 help="The filename containing the FURL of the controller"
125 help="The filename containing the FURL of the controller"
124 )
126 )
125 parser.add_option(
127 parser.add_option(
126 "--mpi",
128 "--mpi",
127 type="string",
129 type="string",
128 dest="mpi",
130 dest="mpi",
129 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
131 help="How to enable MPI (mpi4py, pytrilinos, or empty string to disable)"
130 )
132 )
131 parser.add_option(
133 parser.add_option(
132 "-l",
134 "-l",
133 "--logfile",
135 "--logfile",
134 type="string",
136 type="string",
135 dest="logfile",
137 dest="logfile",
136 help="log file name (default is stdout)"
138 help="log file name (default is stdout)"
137 )
139 )
138 parser.add_option(
140 parser.add_option(
139 "--ipythondir",
141 "--ipythondir",
140 type="string",
142 type="string",
141 dest="ipythondir",
143 dest="ipythondir",
142 help="look for config files and profiles in this directory"
144 help="look for config files and profiles in this directory"
143 )
145 )
144
146
145 (options, args) = parser.parse_args()
147 (options, args) = parser.parse_args()
146
148
147 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
149 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
148 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
150 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
149
151
150 kernel_config = kernel_config_manager.get_config_obj()
152 kernel_config = kernel_config_manager.get_config_obj()
151 # Now override with command line options
153 # Now override with command line options
152 if options.furl_file is not None:
154 if options.furl_file is not None:
153 kernel_config['engine']['furl_file'] = options.furl_file
155 kernel_config['engine']['furl_file'] = options.furl_file
154 if options.logfile is not None:
156 if options.logfile is not None:
155 kernel_config['engine']['logfile'] = options.logfile
157 kernel_config['engine']['logfile'] = options.logfile
156 if options.mpi is not None:
158 if options.mpi is not None:
157 kernel_config['mpi']['default'] = options.mpi
159 kernel_config['mpi']['default'] = options.mpi
158
160
159
161
160 def main():
162 def main():
161 """
163 """
162 After creating the configuration information, start the engine.
164 After creating the configuration information, start the engine.
163 """
165 """
164 init_config()
166 init_config()
165 start_engine()
167 start_engine()
166
168
167
169
168 if __name__ == "__main__":
170 if __name__ == "__main__":
169 main() No newline at end of file
171 main()
@@ -1,35 +1,47 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """ Proxy module for accessing platform specific utility functions.
2 """ Proxy module for accessing platform specific utility functions.
3
3
4 Importing this module should give you the implementations that are correct
4 Importing this module should give you the implementations that are correct
5 for your operation system, from platutils_PLATFORMNAME module.
5 for your operation system, from platutils_PLATFORMNAME module.
6
7 $Id: ipstruct.py 1005 2006-01-12 08:39:26Z fperez $
8
9
10 """
6 """
11
7
12
13 #*****************************************************************************
8 #*****************************************************************************
14 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
15 #
10 #
16 # 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
17 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
18 #*****************************************************************************
13 #*****************************************************************************
19
14
20 from IPython import Release
15 from IPython import Release
21 __author__ = '%s <%s>' % Release.authors['Ville']
16 __author__ = '%s <%s>' % Release.authors['Ville']
22 __license__ = Release.license
17 __license__ = Release.license
23
18
24 import os,sys
19 import os
20 import sys
25
21
22 # Import the platform-specific implementations
26 if os.name == 'posix':
23 if os.name == 'posix':
27 from platutils_posix import *
24 import platutils_posix as _platutils
28 elif sys.platform == 'win32':
25 elif sys.platform == 'win32':
29 from platutils_win32 import *
26 import platutils_win32 as _platutils
30 else:
27 else:
31 from platutils_dummy import *
28 import platutils_dummy as _platutils
32 import warnings
29 import warnings
33 warnings.warn("Platutils not available for platform '%s', some features may be missing" %
30 warnings.warn("Platutils not available for platform '%s', some features may be missing" %
34 os.name)
31 os.name)
35 del warnings
32 del warnings
33
34
35 # Functionality that's logically common to all platforms goes here, each
36 # platform-specific module only provides the bits that are OS-dependent.
37
38 def freeze_term_title():
39 _platutils.ignore_termtitle = True
40
41
42 def set_term_title(title):
43 """Set terminal title using the necessary platform-dependent calls."""
44
45 if _platutils.ignore_termtitle:
46 return
47 _platutils.set_term_title(title)
@@ -1,29 +1,24 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """ Platform specific utility functions, dummy version
2 """ Platform specific utility functions, dummy version
3
3
4 This has empty implementation of the platutils functions, used for
4 This has empty implementation of the platutils functions, used for
5 unsupported operating systems.
5 unsupported operating systems.
6
7 $Id: ipstruct.py 1005 2006-01-12 08:39:26Z fperez $
8
9
10 """
6 """
11
7
12
13 #*****************************************************************************
8 #*****************************************************************************
14 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
15 #
10 #
16 # 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
17 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
18 #*****************************************************************************
13 #*****************************************************************************
19
14
20 from IPython import Release
15 from IPython import Release
21 __author__ = '%s <%s>' % Release.authors['Ville']
16 __author__ = '%s <%s>' % Release.authors['Ville']
22 __license__ = Release.license
17 __license__ = Release.license
23
18
19 # This variable is part of the expected API of the module:
20 ignore_termtitle = True
24
21
25 def _dummy(*args,**kw):
22 def set_term_title(*args,**kw):
23 """Dummy no-op."""
26 pass
24 pass
27
28 set_term_title = _dummy
29
@@ -1,47 +1,36 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """ Platform specific utility functions, posix version
2 """ Platform specific utility functions, posix version
3
3
4 Importing this module directly is not portable - rather, import platutils
4 Importing this module directly is not portable - rather, import platutils
5 to use these functions in platform agnostic fashion.
5 to use these functions in platform agnostic fashion.
6
7 $Id: ipstruct.py 1005 2006-01-12 08:39:26Z fperez $
8
9 """
6 """
10
7
11
12 #*****************************************************************************
8 #*****************************************************************************
13 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
14 #
10 #
15 # 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
16 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
17 #*****************************************************************************
13 #*****************************************************************************
18
14
19 from IPython import Release
15 from IPython import Release
20 __author__ = '%s <%s>' % Release.authors['Ville']
16 __author__ = '%s <%s>' % Release.authors['Ville']
21 __license__ = Release.license
17 __license__ = Release.license
22
18
23 import sys
19 import sys
24 import os
20 import os
25
21
26 ignore_termtitle = False
22 ignore_termtitle = False
27
23
28 def _dummy_op(*a, **b):
24 def _dummy_op(*a, **b):
29 """ A no-op function """
25 """ A no-op function """
30
26
31 def _set_term_title_xterm(title):
27 def _set_term_title_xterm(title):
32 """ Change virtual terminal title in xterm-workalikes """
28 """ Change virtual terminal title in xterm-workalikes """
33
29
34 if ignore_termtitle:
35 return
36
37 sys.stdout.write('\033]%d;%s\007' % (0,title))
30 sys.stdout.write('\033]%d;%s\007' % (0,title))
38
31
39
32
40 if os.environ.get('TERM','') == 'xterm':
33 if os.environ.get('TERM','') == 'xterm':
41 set_term_title = _set_term_title_xterm
34 set_term_title = _set_term_title_xterm
42 else:
35 else:
43 set_term_title = _dummy_op
36 set_term_title = _dummy_op
44
45 def freeze_term_title():
46 global ignore_termtitle
47 ignore_termtitle = True
@@ -1,56 +1,47 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """ Platform specific utility functions, win32 version
2 """ Platform specific utility functions, win32 version
3
3
4 Importing this module directly is not portable - rather, import platutils
4 Importing this module directly is not portable - rather, import platutils
5 to use these functions in platform agnostic fashion.
5 to use these functions in platform agnostic fashion.
6
7 $Id: ipstruct.py 1005 2006-01-12 08:39:26Z fperez $
8
9 """
6 """
10
7
11
12 #*****************************************************************************
8 #*****************************************************************************
13 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
14 #
10 #
15 # 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
16 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
17 #*****************************************************************************
13 #*****************************************************************************
18
14
19 from IPython import Release
15 from IPython import Release
20 __author__ = '%s <%s>' % Release.authors['Ville']
16 __author__ = '%s <%s>' % Release.authors['Ville']
21 __license__ = Release.license
17 __license__ = Release.license
22
18
23 import os
19 import os
24
20
25 ignore_termtitle = 0
21 ignore_termtitle = False
26
22
27 try:
23 try:
28 import ctypes
24 import ctypes
29 SetConsoleTitleW=ctypes.windll.kernel32.SetConsoleTitleW
25
30 SetConsoleTitleW.argtypes=[ctypes.c_wchar_p]
26 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
31 def _set_term_title(title):
27 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
32 """ Set terminal title using the ctypes"""
28
29 def set_term_title(title):
30 """Set terminal title using ctypes to access the Win32 APIs."""
33 SetConsoleTitleW(title)
31 SetConsoleTitleW(title)
34
32
35 except ImportError:
33 except ImportError:
36 def _set_term_title(title):
34 def set_term_title(title):
37 """ Set terminal title using the 'title' command """
35 """Set terminal title using the 'title' command."""
38 curr=os.getcwd()
36 global ignore_termtitle
39 os.chdir("C:") #Cannot be on network share when issuing system commands
37
40 ret = os.system("title " + title)
38 try:
41 os.chdir(curr)
39 # Cannot be on network share when issuing system commands
40 curr = os.getcwd()
41 os.chdir("C:")
42 ret = os.system("title " + title)
43 finally:
44 os.chdir(curr)
42 if ret:
45 if ret:
43 ignore_termtitle = 1
46 # non-zero return code signals error, don't try again
44
47 ignore_termtitle = True
45 def set_term_title(title):
46 """ Set terminal title using the 'title' command """
47 global ignore_termtitle
48
49 if ignore_termtitle:
50 return
51 _set_term_title(title)
52
53 def freeze_term_title():
54 global ignore_termtitle
55 ignore_termtitle = 1
56
1 NO CONTENT: file renamed from IPython/testing/ipdoctest.py to IPython/testing/attic/ipdoctest.py
NO CONTENT: file renamed from IPython/testing/ipdoctest.py to IPython/testing/attic/ipdoctest.py
1 NO CONTENT: file renamed from IPython/testing/parametric.py to IPython/testing/attic/parametric.py
NO CONTENT: file renamed from IPython/testing/parametric.py to IPython/testing/attic/parametric.py
1 NO CONTENT: file renamed from IPython/testing/tcommon.py to IPython/testing/attic/tcommon.py
NO CONTENT: file renamed from IPython/testing/tcommon.py to IPython/testing/attic/tcommon.py
1 NO CONTENT: file renamed from IPython/testing/testTEMPLATE.py to IPython/testing/attic/testTEMPLATE.py
NO CONTENT: file renamed from IPython/testing/testTEMPLATE.py to IPython/testing/attic/testTEMPLATE.py
1 NO CONTENT: file renamed from IPython/testing/tstTEMPLATE_doctest.py to IPython/testing/attic/tstTEMPLATE_doctest.py
NO CONTENT: file renamed from IPython/testing/tstTEMPLATE_doctest.py to IPython/testing/attic/tstTEMPLATE_doctest.py
1 NO CONTENT: file renamed from IPython/testing/tstTEMPLATE_doctest.txt to IPython/testing/attic/tstTEMPLATE_doctest.txt
NO CONTENT: file renamed from IPython/testing/tstTEMPLATE_doctest.txt to IPython/testing/attic/tstTEMPLATE_doctest.txt
@@ -1,72 +1,86 b''
1 """Utilities for testing code.
1 """DEPRECATED - use IPython.testing.util instead.
2
3 Utilities for testing code.
2 """
4 """
3
5
6 #############################################################################
7
8 # This was old testing code we never really used in IPython. The pieces of
9 # testing machinery from snakeoil that were good have already been merged into
10 # the nose plugin, so this can be taken away soon. Leave a warning for now,
11 # we'll remove it in a later release (around 0.10 or so).
12 from warnings import warn
13 warn('This will be removed soon. Use IPython.testing.util instead',
14 DeprecationWarning)
15
16 #############################################################################
17
4 # Required modules and packages
18 # Required modules and packages
5
19
6 # Standard Python lib
20 # Standard Python lib
7 import os
21 import os
8 import sys
22 import sys
9
23
10 # From this project
24 # From this project
11 from IPython.tools import utils
25 from IPython.tools import utils
12
26
13 # path to our own installation, so we can find source files under this.
27 # path to our own installation, so we can find source files under this.
14 TEST_PATH = os.path.dirname(os.path.abspath(__file__))
28 TEST_PATH = os.path.dirname(os.path.abspath(__file__))
15
29
16 # Global flag, used by vprint
30 # Global flag, used by vprint
17 VERBOSE = '-v' in sys.argv or '--verbose' in sys.argv
31 VERBOSE = '-v' in sys.argv or '--verbose' in sys.argv
18
32
19 ##########################################################################
33 ##########################################################################
20 # Code begins
34 # Code begins
21
35
22 # Some utility functions
36 # Some utility functions
23 def vprint(*args):
37 def vprint(*args):
24 """Print-like function which relies on a global VERBOSE flag."""
38 """Print-like function which relies on a global VERBOSE flag."""
25 if not VERBOSE:
39 if not VERBOSE:
26 return
40 return
27
41
28 write = sys.stdout.write
42 write = sys.stdout.write
29 for item in args:
43 for item in args:
30 write(str(item))
44 write(str(item))
31 write('\n')
45 write('\n')
32 sys.stdout.flush()
46 sys.stdout.flush()
33
47
34 def test_path(path):
48 def test_path(path):
35 """Return a path as a subdir of the test package.
49 """Return a path as a subdir of the test package.
36
50
37 This finds the correct path of the test package on disk, and prepends it
51 This finds the correct path of the test package on disk, and prepends it
38 to the input path."""
52 to the input path."""
39
53
40 return os.path.join(TEST_PATH,path)
54 return os.path.join(TEST_PATH,path)
41
55
42 def fullPath(startPath,files):
56 def fullPath(startPath,files):
43 """Make full paths for all the listed files, based on startPath.
57 """Make full paths for all the listed files, based on startPath.
44
58
45 Only the base part of startPath is kept, since this routine is typically
59 Only the base part of startPath is kept, since this routine is typically
46 used with a script's __file__ variable as startPath. The base of startPath
60 used with a script's __file__ variable as startPath. The base of startPath
47 is then prepended to all the listed files, forming the output list.
61 is then prepended to all the listed files, forming the output list.
48
62
49 :Parameters:
63 :Parameters:
50 startPath : string
64 startPath : string
51 Initial path to use as the base for the results. This path is split
65 Initial path to use as the base for the results. This path is split
52 using os.path.split() and only its first component is kept.
66 using os.path.split() and only its first component is kept.
53
67
54 files : string or list
68 files : string or list
55 One or more files.
69 One or more files.
56
70
57 :Examples:
71 :Examples:
58
72
59 >>> fullPath('/foo/bar.py',['a.txt','b.txt'])
73 >>> fullPath('/foo/bar.py',['a.txt','b.txt'])
60 ['/foo/a.txt', '/foo/b.txt']
74 ['/foo/a.txt', '/foo/b.txt']
61
75
62 >>> fullPath('/foo',['a.txt','b.txt'])
76 >>> fullPath('/foo',['a.txt','b.txt'])
63 ['/a.txt', '/b.txt']
77 ['/a.txt', '/b.txt']
64
78
65 If a single file is given, the output is still a list:
79 If a single file is given, the output is still a list:
66 >>> fullPath('/foo','a.txt')
80 >>> fullPath('/foo','a.txt')
67 ['/a.txt']
81 ['/a.txt']
68 """
82 """
69
83
70 files = utils.list_strings(files)
84 files = utils.list_strings(files)
71 base = os.path.split(startPath)[0]
85 base = os.path.split(startPath)[0]
72 return [ os.path.join(base,f) for f in files ]
86 return [ os.path.join(base,f) for f in files ]
@@ -1,42 +1,17 b''
1 =========================================
1 =========================================
2 Doctests for the ``tools.utils`` module
2 Doctests for the ``tools.utils`` module
3 =========================================
3 =========================================
4
4
5 The way doctest loads these, the entire document is applied as a single test
5 The way doctest loads these, the entire document is applied as a single test
6 rather than multiple individual ones, unfortunately.
6 rather than multiple individual ones, unfortunately::
7
7
8
9 Auto-generated tests
10 ====================
11
12
13 ----------------------------------------------------------------------------
14
15 Begin included file tst_tools_utils_doctest2.py::
16
17 # Setup - all imports are done in tcommon
18 >>> from IPython.testing import tcommon
19 >>> from IPython.testing.tcommon import *
20
21 # Doctest code begins here
22 >>> from IPython.tools import utils
8 >>> from IPython.tools import utils
23
9
24 # Some other tests for utils
10 # Some other tests for utils
25
11
26 >>> utils.marquee('Testing marquee')
12 >>> utils.marquee('Testing marquee')
27 '****************************** Testing marquee ******************************'
13 '****************************** Testing marquee ******************************'
28
14
29 >>> utils.marquee('Another test',30,'.')
15 >>> utils.marquee('Another test',30,'.')
30 '........ Another test ........'
16 '........ Another test ........'
31
32
33 End included file tst_tools_utils_doctest2.py
34
35 ----------------------------------------------------------------------------
36
37
38
39 Manually generated tests
40 ========================
41
17
42 These are one-off tests written by hand, copied from an interactive prompt.
@@ -1,31 +1,30 b''
1 include README_Windows.txt
1 include README_Windows.txt
2 include win32_manual_post_install.py
2 include win32_manual_post_install.py
3 include ipython.py
3 include ipython.py
4
4
5 graft scripts
5 graft scripts
6
6
7 graft setupext
7 graft setupext
8
8
9 graft IPython/UserConfig
9 graft IPython/UserConfig
10
10
11 graft IPython/kernel
11 graft IPython/kernel
12 graft IPython/config
12 graft IPython/config
13 graft IPython/testing
13 graft IPython/testing
14 graft IPython/tools
14 graft IPython/tools
15
15
16 graft docs
16 graft docs
17 exclude docs/\#*
17 exclude docs/\#*
18 exclude docs/man/*.1
18 exclude docs/man/*.1
19 exclude docs/ChangeLog.*
20
19
21 # There seems to be no way of excluding whole subdirectories, other than
20 # There seems to be no way of excluding whole subdirectories, other than
22 # manually excluding all their subdirs. distutils really is horrible...
21 # manually excluding all their subdirs. distutils really is horrible...
23 exclude docs/attic/*
22 exclude docs/attic/*
24 exclude docs/build/*
23 exclude docs/build/*
25
24
26 global-exclude *~
25 global-exclude *~
27 global-exclude *.flc
26 global-exclude *.flc
28 global-exclude *.pyc
27 global-exclude *.pyc
29 global-exclude .dircopy.log
28 global-exclude .dircopy.log
30 global-exclude .svn
29 global-exclude .svn
31 global-exclude .bzr
30 global-exclude .bzr
@@ -1,483 +1,491 b''
1 ;;; ipython.el --- Adds support for IPython to python-mode.el
1 ;;; ipython.el --- Adds support for IPython to python-mode.el
2
2
3 ;; Copyright (C) 2002, 2003, 2004, 2005 Alexander Schmolck
3 ;; Copyright (C) 2002, 2003, 2004, 2005 Alexander Schmolck
4 ;; Author: Alexander Schmolck
4 ;; Author: Alexander Schmolck
5 ;; Keywords: ipython python languages oop
5 ;; Keywords: ipython python languages oop
6 ;; URL: http://ipython.scipy.org
6 ;; URL: http://ipython.scipy.org
7 ;; Compatibility: Emacs21, XEmacs21
7 ;; Compatibility: Emacs21, XEmacs21
8 ;; FIXME: #$@! INPUT RING
8 ;; FIXME: #$@! INPUT RING
9 (defconst ipython-version "$Revision: 2927 $"
9 (defconst ipython-version "$Revision: 2927 $"
10 "VC version number.")
10 "VC version number.")
11
11
12 ;;; Commentary
12 ;;; Commentary
13 ;; This library makes all the functionality python-mode has when running with
13 ;; This library makes all the functionality python-mode has when running with
14 ;; the normal python-interpreter available for ipython, too. It also enables a
14 ;; the normal python-interpreter available for ipython, too. It also enables a
15 ;; persistent py-shell command history across sessions (if you exit python
15 ;; persistent py-shell command history across sessions (if you exit python
16 ;; with C-d in py-shell) and defines the command `ipython-to-doctest', which
16 ;; with C-d in py-shell) and defines the command `ipython-to-doctest', which
17 ;; can be used to convert bits of a ipython session into something that can be
17 ;; can be used to convert bits of a ipython session into something that can be
18 ;; used for doctests. To install, put this file somewhere in your emacs
18 ;; used for doctests. To install, put this file somewhere in your emacs
19 ;; `load-path' [1] and add the following line to your ~/.emacs file (the first
19 ;; `load-path' [1] and add the following line to your ~/.emacs file (the first
20 ;; line only needed if the default (``"ipython"``) is wrong)::
20 ;; line only needed if the default (``"ipython"``) is wrong)::
21 ;;
21 ;;
22 ;; (setq ipython-command "/SOME-PATH/ipython")
22 ;; (setq ipython-command "/SOME-PATH/ipython")
23 ;; (require 'ipython)
23 ;; (require 'ipython)
24 ;;
24 ;;
25 ;; Ipython will be set as the default python shell, but only if the ipython
25 ;; Ipython will be set as the default python shell, but only if the ipython
26 ;; executable is in the path. For ipython sessions autocompletion with <tab>
26 ;; executable is in the path. For ipython sessions autocompletion with <tab>
27 ;; is also enabled (experimental feature!). Please also note that all the
27 ;; is also enabled (experimental feature!). Please also note that all the
28 ;; terminal functions in py-shell are handled by emacs's comint, **not** by
28 ;; terminal functions in py-shell are handled by emacs's comint, **not** by
29 ;; (i)python, so importing readline etc. will have 0 effect.
29 ;; (i)python, so importing readline etc. will have 0 effect.
30 ;;
30 ;;
31 ;; To start an interactive ipython session run `py-shell' with ``M-x py-shell``
31 ;; To start an interactive ipython session run `py-shell' with ``M-x py-shell``
32 ;; (or the default keybinding ``C-c C-!``).
32 ;; (or the default keybinding ``C-c C-!``).
33 ;;
33 ;;
34 ;; You can customize the arguments passed to the IPython instance at startup by
35 ;; setting the ``py-python-command-args`` variable. For example, to start
36 ;; always in ``pylab`` mode with hardcoded light-background colors, you can
37 ;; use::
38 ;;
39 ;; (setq py-python-command-args '("-pylab" "-colors" "LightBG"))
40 ;;
41 ;;
34 ;; NOTE: This mode is currently somewhat alpha and although I hope that it
42 ;; NOTE: This mode is currently somewhat alpha and although I hope that it
35 ;; will work fine for most cases, doing certain things (like the
43 ;; will work fine for most cases, doing certain things (like the
36 ;; autocompletion and a decent scheme to switch between python interpreters)
44 ;; autocompletion and a decent scheme to switch between python interpreters)
37 ;; properly will also require changes to ipython that will likely have to wait
45 ;; properly will also require changes to ipython that will likely have to wait
38 ;; for a larger rewrite scheduled some time in the future.
46 ;; for a larger rewrite scheduled some time in the future.
39 ;;
47 ;;
40 ;; Also note that you currently NEED THE CVS VERSION OF PYTHON.EL.
41 ;;
48 ;;
42 ;; Further note that I don't know whether this runs under windows or not and
49 ;; Further note that I don't know whether this runs under windows or not and
43 ;; that if it doesn't I can't really help much, not being afflicted myself.
50 ;; that if it doesn't I can't really help much, not being afflicted myself.
44 ;;
51 ;;
45 ;;
52 ;;
46 ;; Hints for effective usage
53 ;; Hints for effective usage
47 ;; -------------------------
54 ;; -------------------------
48 ;;
55 ;;
49 ;; - IMO the best feature by far of the ipython/emacs combo is how much easier it
56 ;; - IMO the best feature by far of the ipython/emacs combo is how much easier
50 ;; makes it to find and fix bugs thanks to the ``%pdb on``/ pdbtrack combo. Try
57 ;; it makes it to find and fix bugs thanks to the ``%pdb on or %debug``/
51 ;; it: first in the ipython to shell do ``%pdb on`` then do something that will
58 ;; pdbtrack combo. Try it: first in the ipython to shell do ``%pdb on`` then
52 ;; raise an exception (FIXME nice example) -- and be amazed how easy it is to
59 ;; do something that will raise an exception (FIXME nice example), or type
53 ;; inspect the live objects in each stack frames and to jump to the
60 ;; ``%debug`` after the exception has been raised. YOu'll be amazed at how
54 ;; corresponding sourcecode locations as you walk up and down the stack trace
61 ;; easy it is to inspect the live objects in each stack frames and to jump to
55 ;; (even without ``%pdb on`` you can always use ``C-c -`` (`py-up-exception')
62 ;; the corresponding sourcecode locations as you walk up and down the stack
56 ;; to jump to the corresponding source code locations).
63 ;; trace (even without ``%pdb on`` you can always use ``C-c -``
64 ;; (`py-up-exception') to jump to the corresponding source code locations).
57 ;;
65 ;;
58 ;; - emacs gives you much more powerful commandline editing and output searching
66 ;; - emacs gives you much more powerful commandline editing and output searching
59 ;; capabilities than ipython-standalone -- isearch is your friend if you
67 ;; capabilities than ipython-standalone -- isearch is your friend if you
60 ;; quickly want to print 'DEBUG ...' to stdout out etc.
68 ;; quickly want to print 'DEBUG ...' to stdout out etc.
61 ;;
69 ;;
62 ;; - This is not really specific to ipython, but for more convenient history
70 ;; - This is not really specific to ipython, but for more convenient history
63 ;; access you might want to add something like the following to *the beggining*
71 ;; access you might want to add something like the following to *the beggining*
64 ;; of your ``.emacs`` (if you want behavior that's more similar to stand-alone
72 ;; of your ``.emacs`` (if you want behavior that's more similar to stand-alone
65 ;; ipython, you can change ``meta p`` etc. for ``control p``)::
73 ;; ipython, you can change ``meta p`` etc. for ``control p``)::
66 ;;
74 ;;
67 ;; (require 'comint)
75 ;; (require 'comint)
68 ;; (define-key comint-mode-map [(meta p)]
76 ;; (define-key comint-mode-map [(meta p)]
69 ;; 'comint-previous-matching-input-from-input)
77 ;; 'comint-previous-matching-input-from-input)
70 ;; (define-key comint-mode-map [(meta n)]
78 ;; (define-key comint-mode-map [(meta n)]
71 ;; 'comint-next-matching-input-from-input)
79 ;; 'comint-next-matching-input-from-input)
72 ;; (define-key comint-mode-map [(control meta n)]
80 ;; (define-key comint-mode-map [(control meta n)]
73 ;; 'comint-next-input)
81 ;; 'comint-next-input)
74 ;; (define-key comint-mode-map [(control meta p)]
82 ;; (define-key comint-mode-map [(control meta p)]
75 ;; 'comint-previous-input)
83 ;; 'comint-previous-input)
76 ;;
84 ;;
77 ;; - Be aware that if you customize py-python-command previously, this value
85 ;; - Be aware that if you customize py-python-command previously, this value
78 ;; will override what ipython.el does (because loading the customization
86 ;; will override what ipython.el does (because loading the customization
79 ;; variables comes later).
87 ;; variables comes later).
80 ;;
88 ;;
81 ;; Please send comments and feedback to the ipython-list
89 ;; Please send comments and feedback to the ipython-list
82 ;; (<ipython-user@scipy.net>) where I (a.s.) or someone else will try to
90 ;; (<ipython-user@scipy.org>) where I (a.s.) or someone else will try to
83 ;; answer them (it helps if you specify your emacs version, OS etc;
91 ;; answer them (it helps if you specify your emacs version, OS etc;
84 ;; familiarity with <http://www.catb.org/~esr/faqs/smart-questions.html> might
92 ;; familiarity with <http://www.catb.org/~esr/faqs/smart-questions.html> might
85 ;; speed up things further).
93 ;; speed up things further).
86 ;;
94 ;;
87 ;; Footnotes:
95 ;; Footnotes:
88 ;;
96 ;;
89 ;; [1] If you don't know what `load-path' is, C-h v load-path will tell
97 ;; [1] If you don't know what `load-path' is, C-h v load-path will tell
90 ;; you; if required you can also add a new directory. So assuming that
98 ;; you; if required you can also add a new directory. So assuming that
91 ;; ipython.el resides in ~/el/, put this in your emacs:
99 ;; ipython.el resides in ~/el/, put this in your emacs:
92 ;;
100 ;;
93 ;;
101 ;;
94 ;; (add-to-list 'load-path "~/el")
102 ;; (add-to-list 'load-path "~/el")
95 ;; (setq ipython-command "/some-path/ipython")
103 ;; (setq ipython-command "/some-path/ipython")
96 ;; (require 'ipython)
104 ;; (require 'ipython)
97 ;;
105 ;;
98 ;;
106 ;;
99 ;;
107 ;;
100 ;;
108 ;;
101 ;; TODO:
109 ;; TODO:
102 ;; - do autocompletion properly
110 ;; - do autocompletion properly
103 ;; - implement a proper switching between python interpreters
111 ;; - implement a proper switching between python interpreters
104 ;;
112 ;;
105 ;; BUGS:
113 ;; BUGS:
106 ;; - neither::
114 ;; - neither::
107 ;;
115 ;;
108 ;; (py-shell "-c print 'FOOBAR'")
116 ;; (py-shell "-c print 'FOOBAR'")
109 ;;
117 ;;
110 ;; nor::
118 ;; nor::
111 ;;
119 ;;
112 ;; (let ((py-python-command-args (append py-python-command-args
120 ;; (let ((py-python-command-args (append py-python-command-args
113 ;; '("-c" "print 'FOOBAR'"))))
121 ;; '("-c" "print 'FOOBAR'"))))
114 ;; (py-shell))
122 ;; (py-shell))
115 ;;
123 ;;
116 ;; seem to print anything as they should
124 ;; seem to print anything as they should
117 ;;
125 ;;
118 ;; - look into init priority issues with `py-python-command' (if it's set
126 ;; - look into init priority issues with `py-python-command' (if it's set
119 ;; via custom)
127 ;; via custom)
120
128
121
129
122 ;;; Code
130 ;;; Code
123 (require 'cl)
131 (require 'cl)
124 (require 'shell)
132 (require 'shell)
125 (require 'executable)
133 (require 'executable)
126 (require 'ansi-color)
134 (require 'ansi-color)
127
135
128 (defcustom ipython-command "ipython"
136 (defcustom ipython-command "ipython"
129 "*Shell command used to start ipython."
137 "*Shell command used to start ipython."
130 :type 'string
138 :type 'string
131 :group 'python)
139 :group 'python)
132
140
133 ;; Users can set this to nil
141 ;; Users can set this to nil
134 (defvar py-shell-initial-switch-buffers t
142 (defvar py-shell-initial-switch-buffers t
135 "If nil, don't switch to the *Python* buffer on the first call to
143 "If nil, don't switch to the *Python* buffer on the first call to
136 `py-shell'.")
144 `py-shell'.")
137
145
138 (defvar ipython-backup-of-py-python-command nil
146 (defvar ipython-backup-of-py-python-command nil
139 "HACK")
147 "HACK")
140
148
141
149
142 (defvar ipython-de-input-prompt-regexp "\\(?:
150 (defvar ipython-de-input-prompt-regexp "\\(?:
143 In \\[[0-9]+\\]: *.*
151 In \\[[0-9]+\\]: *.*
144 ----+> \\(.*
152 ----+> \\(.*
145 \\)[\n]?\\)\\|\\(?:
153 \\)[\n]?\\)\\|\\(?:
146 In \\[[0-9]+\\]: *\\(.*
154 In \\[[0-9]+\\]: *\\(.*
147 \\)\\)\\|^[ ]\\{3\\}[.]\\{3,\\}: *\\(.*
155 \\)\\)\\|^[ ]\\{3\\}[.]\\{3,\\}: *\\(.*
148 \\)"
156 \\)"
149 "A regular expression to match the IPython input prompt and the python
157 "A regular expression to match the IPython input prompt and the python
150 command after it. The first match group is for a command that is rewritten,
158 command after it. The first match group is for a command that is rewritten,
151 the second for a 'normal' command, and the third for a multiline command.")
159 the second for a 'normal' command, and the third for a multiline command.")
152 (defvar ipython-de-output-prompt-regexp "^Out\\[[0-9]+\\]: "
160 (defvar ipython-de-output-prompt-regexp "^Out\\[[0-9]+\\]: "
153 "A regular expression to match the output prompt of IPython.")
161 "A regular expression to match the output prompt of IPython.")
154
162
155
163
156 (if (not (executable-find ipython-command))
164 (if (not (executable-find ipython-command))
157 (message (format "Can't find executable %s - ipython.el *NOT* activated!!!"
165 (message (format "Can't find executable %s - ipython.el *NOT* activated!!!"
158 ipython-command))
166 ipython-command))
159 ;; XXX load python-mode, so that we can screw around with its variables
167 ;; XXX load python-mode, so that we can screw around with its variables
160 ;; this has the disadvantage that python-mode is loaded even if no
168 ;; this has the disadvantage that python-mode is loaded even if no
161 ;; python-file is ever edited etc. but it means that `py-shell' works
169 ;; python-file is ever edited etc. but it means that `py-shell' works
162 ;; without loading a python-file first. Obviously screwing around with
170 ;; without loading a python-file first. Obviously screwing around with
163 ;; python-mode's variables like this is a mess, but well.
171 ;; python-mode's variables like this is a mess, but well.
164 (require 'python-mode)
172 (require 'python-mode)
165 ;; turn on ansi colors for ipython and activate completion
173 ;; turn on ansi colors for ipython and activate completion
166 (defun ipython-shell-hook ()
174 (defun ipython-shell-hook ()
167 ;; the following is to synchronize dir-changes
175 ;; the following is to synchronize dir-changes
168 (make-local-variable 'shell-dirstack)
176 (make-local-variable 'shell-dirstack)
169 (setq shell-dirstack nil)
177 (setq shell-dirstack nil)
170 (make-local-variable 'shell-last-dir)
178 (make-local-variable 'shell-last-dir)
171 (setq shell-last-dir nil)
179 (setq shell-last-dir nil)
172 (make-local-variable 'shell-dirtrackp)
180 (make-local-variable 'shell-dirtrackp)
173 (setq shell-dirtrackp t)
181 (setq shell-dirtrackp t)
174 (add-hook 'comint-input-filter-functions 'shell-directory-tracker nil t)
182 (add-hook 'comint-input-filter-functions 'shell-directory-tracker nil t)
175
183
176 (ansi-color-for-comint-mode-on)
184 (ansi-color-for-comint-mode-on)
177 (define-key py-shell-map [tab] 'ipython-complete)
185 (define-key py-shell-map [tab] 'ipython-complete)
178 ;; Add this so that tab-completion works both in X11 frames and inside
186 ;; Add this so that tab-completion works both in X11 frames and inside
179 ;; terminals (such as when emacs is called with -nw).
187 ;; terminals (such as when emacs is called with -nw).
180 (define-key py-shell-map "\t" 'ipython-complete)
188 (define-key py-shell-map "\t" 'ipython-complete)
181 ;;XXX this is really just a cheap hack, it only completes symbols in the
189 ;;XXX this is really just a cheap hack, it only completes symbols in the
182 ;;interactive session -- useful nonetheless.
190 ;;interactive session -- useful nonetheless.
183 (define-key py-mode-map [(meta tab)] 'ipython-complete)
191 (define-key py-mode-map [(meta tab)] 'ipython-complete)
184
192
185 )
193 )
186 (add-hook 'py-shell-hook 'ipython-shell-hook)
194 (add-hook 'py-shell-hook 'ipython-shell-hook)
187 ;; Regular expression that describes tracebacks for IPython in context and
195 ;; Regular expression that describes tracebacks for IPython in context and
188 ;; verbose mode.
196 ;; verbose mode.
189
197
190 ;;Adapt python-mode settings for ipython.
198 ;;Adapt python-mode settings for ipython.
191 ;; (this works for %xmode 'verbose' or 'context')
199 ;; (this works for %xmode 'verbose' or 'context')
192
200
193 ;; XXX putative regexps for syntax errors; unfortunately the
201 ;; XXX putative regexps for syntax errors; unfortunately the
194 ;; current python-mode traceback-line-re scheme is too primitive,
202 ;; current python-mode traceback-line-re scheme is too primitive,
195 ;; so it's either matching syntax errors, *or* everything else
203 ;; so it's either matching syntax errors, *or* everything else
196 ;; (XXX: should ask Fernando for a change)
204 ;; (XXX: should ask Fernando for a change)
197 ;;"^ File \"\\(.*?\\)\", line \\([0-9]+\\).*\n.*\n.*\nSyntaxError:"
205 ;;"^ File \"\\(.*?\\)\", line \\([0-9]+\\).*\n.*\n.*\nSyntaxError:"
198 ;;^ File \"\\(.*?\\)\", line \\([0-9]+\\)"
206 ;;^ File \"\\(.*?\\)\", line \\([0-9]+\\)"
199
207
200 (setq py-traceback-line-re
208 (setq py-traceback-line-re
201 "\\(^[^\t >].+?\\.py\\).*\n +[0-9]+[^\00]*?\n-+> \\([0-9]+\\)+")
209 "\\(^[^\t >].+?\\.py\\).*\n +[0-9]+[^\00]*?\n-+> \\([0-9]+\\)+")
202
210
203
211
204 ;; Recognize the ipython pdb, whose prompt is 'ipdb>' or 'ipydb>'
212 ;; Recognize the ipython pdb, whose prompt is 'ipdb>' or 'ipydb>'
205 ;;instead of '(Pdb)'
213 ;;instead of '(Pdb)'
206 (setq py-pdbtrack-input-prompt "\n[(<]*[Ii]?[Pp]y?db[>)]+ ")
214 (setq py-pdbtrack-input-prompt "\n[(<]*[Ii]?[Pp]y?db[>)]+ ")
207 (setq pydb-pydbtrack-input-prompt "\n[(]*ipydb[>)]+ ")
215 (setq pydb-pydbtrack-input-prompt "\n[(]*ipydb[>)]+ ")
208
216
209 (setq py-shell-input-prompt-1-regexp "^In \\[[0-9]+\\]: *"
217 (setq py-shell-input-prompt-1-regexp "^In \\[[0-9]+\\]: *"
210 py-shell-input-prompt-2-regexp "^ [.][.][.]+: *" )
218 py-shell-input-prompt-2-regexp "^ [.][.][.]+: *" )
211 ;; select a suitable color-scheme
219 ;; select a suitable color-scheme
212 (unless (member "-colors" py-python-command-args)
220 (unless (member "-colors" py-python-command-args)
213 (setq py-python-command-args
221 (setq py-python-command-args
214 (nconc py-python-command-args
222 (nconc py-python-command-args
215 (list "-colors"
223 (list "-colors"
216 (cond
224 (cond
217 ((eq frame-background-mode 'dark)
225 ((eq frame-background-mode 'dark)
218 "Linux")
226 "Linux")
219 ((eq frame-background-mode 'light)
227 ((eq frame-background-mode 'light)
220 "LightBG")
228 "LightBG")
221 (t ; default (backg-mode isn't always set by XEmacs)
229 (t ; default (backg-mode isn't always set by XEmacs)
222 "LightBG"))))))
230 "LightBG"))))))
223 (unless (equal ipython-backup-of-py-python-command py-python-command)
231 (unless (equal ipython-backup-of-py-python-command py-python-command)
224 (setq ipython-backup-of-py-python-command py-python-command))
232 (setq ipython-backup-of-py-python-command py-python-command))
225 (setq py-python-command ipython-command))
233 (setq py-python-command ipython-command))
226
234
227
235
228 ;; MODIFY py-shell so that it loads the editing history
236 ;; MODIFY py-shell so that it loads the editing history
229 (defadvice py-shell (around py-shell-with-history)
237 (defadvice py-shell (around py-shell-with-history)
230 "Add persistent command-history support (in
238 "Add persistent command-history support (in
231 $PYTHONHISTORY (or \"~/.ipython/history\", if we use IPython)). Also, if
239 $PYTHONHISTORY (or \"~/.ipython/history\", if we use IPython)). Also, if
232 `py-shell-initial-switch-buffers' is nil, it only switches to *Python* if that
240 `py-shell-initial-switch-buffers' is nil, it only switches to *Python* if that
233 buffer already exists."
241 buffer already exists."
234 (if (comint-check-proc "*Python*")
242 (if (comint-check-proc "*Python*")
235 ad-do-it
243 ad-do-it
236 (setq comint-input-ring-file-name
244 (setq comint-input-ring-file-name
237 (if (string-equal py-python-command ipython-command)
245 (if (string-equal py-python-command ipython-command)
238 (concat (or (getenv "IPYTHONDIR") "~/.ipython") "/history")
246 (concat (or (getenv "IPYTHONDIR") "~/.ipython") "/history")
239 (or (getenv "PYTHONHISTORY") "~/.python-history.py")))
247 (or (getenv "PYTHONHISTORY") "~/.python-history.py")))
240 (comint-read-input-ring t)
248 (comint-read-input-ring t)
241 (let ((buf (current-buffer)))
249 (let ((buf (current-buffer)))
242 ad-do-it
250 ad-do-it
243 (unless py-shell-initial-switch-buffers
251 (unless py-shell-initial-switch-buffers
244 (switch-to-buffer-other-window buf)))))
252 (switch-to-buffer-other-window buf)))))
245 (ad-activate 'py-shell)
253 (ad-activate 'py-shell)
246 ;; (defadvice py-execute-region (before py-execute-buffer-ensure-process)
254 ;; (defadvice py-execute-region (before py-execute-buffer-ensure-process)
247 ;; "HACK: test that ipython is already running before executing something.
255 ;; "HACK: test that ipython is already running before executing something.
248 ;; Doing this properly seems not worth the bother (unless people actually
256 ;; Doing this properly seems not worth the bother (unless people actually
249 ;; request it)."
257 ;; request it)."
250 ;; (unless (comint-check-proc "*Python*")
258 ;; (unless (comint-check-proc "*Python*")
251 ;; (error "Sorry you have to first do M-x py-shell to send something to ipython.")))
259 ;; (error "Sorry you have to first do M-x py-shell to send something to ipython.")))
252 ;; (ad-activate 'py-execute-region)
260 ;; (ad-activate 'py-execute-region)
253
261
254 (defadvice py-execute-region (around py-execute-buffer-ensure-process)
262 (defadvice py-execute-region (around py-execute-buffer-ensure-process)
255 "HACK: if `py-shell' is not active or ASYNC is explicitly desired, fall back
263 "HACK: if `py-shell' is not active or ASYNC is explicitly desired, fall back
256 to python instead of ipython."
264 to python instead of ipython."
257 (let ((py-which-shell (if (and (comint-check-proc "*Python*") (not async))
265 (let ((py-which-shell (if (and (comint-check-proc "*Python*") (not async))
258 py-python-command
266 py-python-command
259 ipython-backup-of-py-python-command)))
267 ipython-backup-of-py-python-command)))
260 ad-do-it))
268 ad-do-it))
261 (ad-activate 'py-execute-region)
269 (ad-activate 'py-execute-region)
262
270
263 (defun ipython-to-doctest (start end)
271 (defun ipython-to-doctest (start end)
264 "Transform a cut-and-pasted bit from an IPython session into something that
272 "Transform a cut-and-pasted bit from an IPython session into something that
265 looks like it came from a normal interactive python session, so that it can
273 looks like it came from a normal interactive python session, so that it can
266 be used in doctests. Example:
274 be used in doctests. Example:
267
275
268
276
269 In [1]: import sys
277 In [1]: import sys
270
278
271 In [2]: sys.stdout.write 'Hi!\n'
279 In [2]: sys.stdout.write 'Hi!\n'
272 ------> sys.stdout.write ('Hi!\n')
280 ------> sys.stdout.write ('Hi!\n')
273 Hi!
281 Hi!
274
282
275 In [3]: 3 + 4
283 In [3]: 3 + 4
276 Out[3]: 7
284 Out[3]: 7
277
285
278 gets converted to:
286 gets converted to:
279
287
280 >>> import sys
288 >>> import sys
281 >>> sys.stdout.write ('Hi!\n')
289 >>> sys.stdout.write ('Hi!\n')
282 Hi!
290 Hi!
283 >>> 3 + 4
291 >>> 3 + 4
284 7
292 7
285
293
286 "
294 "
287 (interactive "*r\n")
295 (interactive "*r\n")
288 ;(message (format "###DEBUG s:%de:%d" start end))
296 ;(message (format "###DEBUG s:%de:%d" start end))
289 (save-excursion
297 (save-excursion
290 (save-match-data
298 (save-match-data
291 ;; replace ``In [3]: bla`` with ``>>> bla`` and
299 ;; replace ``In [3]: bla`` with ``>>> bla`` and
292 ;; ``... : bla`` with ``... bla``
300 ;; ``... : bla`` with ``... bla``
293 (goto-char start)
301 (goto-char start)
294 (while (re-search-forward ipython-de-input-prompt-regexp end t)
302 (while (re-search-forward ipython-de-input-prompt-regexp end t)
295 ;(message "finding 1")
303 ;(message "finding 1")
296 (cond ((match-string 3) ;continued
304 (cond ((match-string 3) ;continued
297 (replace-match "... \\3" t nil))
305 (replace-match "... \\3" t nil))
298 (t
306 (t
299 (replace-match ">>> \\1\\2" t nil))))
307 (replace-match ">>> \\1\\2" t nil))))
300 ;; replace ``
308 ;; replace ``
301 (goto-char start)
309 (goto-char start)
302 (while (re-search-forward ipython-de-output-prompt-regexp end t)
310 (while (re-search-forward ipython-de-output-prompt-regexp end t)
303 (replace-match "" t nil)))))
311 (replace-match "" t nil)))))
304
312
305 (defvar ipython-completion-command-string
313 (defvar ipython-completion-command-string
306 "print ';'.join(__IP.Completer.all_completions('%s')) #PYTHON-MODE SILENT\n"
314 "print ';'.join(__IP.Completer.all_completions('%s')) #PYTHON-MODE SILENT\n"
307 "The string send to ipython to query for all possible completions")
315 "The string send to ipython to query for all possible completions")
308
316
309
317
310 ;; xemacs doesn't have `comint-preoutput-filter-functions' so we'll try the
318 ;; xemacs doesn't have `comint-preoutput-filter-functions' so we'll try the
311 ;; following wonderful hack to work around this case
319 ;; following wonderful hack to work around this case
312 (if (featurep 'xemacs)
320 (if (featurep 'xemacs)
313 ;;xemacs
321 ;;xemacs
314 (defun ipython-complete ()
322 (defun ipython-complete ()
315 "Try to complete the python symbol before point. Only knows about the stuff
323 "Try to complete the python symbol before point. Only knows about the stuff
316 in the current *Python* session."
324 in the current *Python* session."
317 (interactive)
325 (interactive)
318 (let* ((ugly-return nil)
326 (let* ((ugly-return nil)
319 (sep ";")
327 (sep ";")
320 (python-process (or (get-buffer-process (current-buffer))
328 (python-process (or (get-buffer-process (current-buffer))
321 ;XXX hack for .py buffers
329 ;XXX hack for .py buffers
322 (get-process py-which-bufname)))
330 (get-process py-which-bufname)))
323 ;; XXX currently we go backwards to find the beginning of an
331 ;; XXX currently we go backwards to find the beginning of an
324 ;; expression part; a more powerful approach in the future might be
332 ;; expression part; a more powerful approach in the future might be
325 ;; to let ipython have the complete line, so that context can be used
333 ;; to let ipython have the complete line, so that context can be used
326 ;; to do things like filename completion etc.
334 ;; to do things like filename completion etc.
327 (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_." (point-at-bol))
335 (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_." (point-at-bol))
328 (point)))
336 (point)))
329 (end (point))
337 (end (point))
330 (pattern (buffer-substring-no-properties beg end))
338 (pattern (buffer-substring-no-properties beg end))
331 (completions nil)
339 (completions nil)
332 (completion-table nil)
340 (completion-table nil)
333 completion
341 completion
334 (comint-output-filter-functions
342 (comint-output-filter-functions
335 (append comint-output-filter-functions
343 (append comint-output-filter-functions
336 '(ansi-color-filter-apply
344 '(ansi-color-filter-apply
337 (lambda (string)
345 (lambda (string)
338 ;(message (format "DEBUG filtering: %s" string))
346 ;(message (format "DEBUG filtering: %s" string))
339 (setq ugly-return (concat ugly-return string))
347 (setq ugly-return (concat ugly-return string))
340 (delete-region comint-last-output-start
348 (delete-region comint-last-output-start
341 (process-mark (get-buffer-process (current-buffer)))))))))
349 (process-mark (get-buffer-process (current-buffer)))))))))
342 ;(message (format "#DEBUG pattern: '%s'" pattern))
350 ;(message (format "#DEBUG pattern: '%s'" pattern))
343 (process-send-string python-process
351 (process-send-string python-process
344 (format ipython-completion-command-string pattern))
352 (format ipython-completion-command-string pattern))
345 (accept-process-output python-process)
353 (accept-process-output python-process)
346 ;(message (format "DEBUG return: %s" ugly-return))
354 ;(message (format "DEBUG return: %s" ugly-return))
347 (setq completions
355 (setq completions
348 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
356 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
349 (setq completion-table (loop for str in completions
357 (setq completion-table (loop for str in completions
350 collect (list str nil)))
358 collect (list str nil)))
351 (setq completion (try-completion pattern completion-table))
359 (setq completion (try-completion pattern completion-table))
352 (cond ((eq completion t))
360 (cond ((eq completion t))
353 ((null completion)
361 ((null completion)
354 (message "Can't find completion for \"%s\"" pattern)
362 (message "Can't find completion for \"%s\"" pattern)
355 (ding))
363 (ding))
356 ((not (string= pattern completion))
364 ((not (string= pattern completion))
357 (delete-region beg end)
365 (delete-region beg end)
358 (insert completion))
366 (insert completion))
359 (t
367 (t
360 (message "Making completion list...")
368 (message "Making completion list...")
361 (with-output-to-temp-buffer "*Python Completions*"
369 (with-output-to-temp-buffer "*Python Completions*"
362 (display-completion-list (all-completions pattern completion-table)))
370 (display-completion-list (all-completions pattern completion-table)))
363 (message "Making completion list...%s" "done")))))
371 (message "Making completion list...%s" "done")))))
364 ;; emacs
372 ;; emacs
365 (defun ipython-complete ()
373 (defun ipython-complete ()
366 "Try to complete the python symbol before point. Only knows about the stuff
374 "Try to complete the python symbol before point. Only knows about the stuff
367 in the current *Python* session."
375 in the current *Python* session."
368 (interactive)
376 (interactive)
369 (let* ((ugly-return nil)
377 (let* ((ugly-return nil)
370 (sep ";")
378 (sep ";")
371 (python-process (or (get-buffer-process (current-buffer))
379 (python-process (or (get-buffer-process (current-buffer))
372 ;XXX hack for .py buffers
380 ;XXX hack for .py buffers
373 (get-process py-which-bufname)))
381 (get-process py-which-bufname)))
374 ;; XXX currently we go backwards to find the beginning of an
382 ;; XXX currently we go backwards to find the beginning of an
375 ;; expression part; a more powerful approach in the future might be
383 ;; expression part; a more powerful approach in the future might be
376 ;; to let ipython have the complete line, so that context can be used
384 ;; to let ipython have the complete line, so that context can be used
377 ;; to do things like filename completion etc.
385 ;; to do things like filename completion etc.
378 (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_./" (point-at-bol))
386 (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_./" (point-at-bol))
379 (point)))
387 (point)))
380 (end (point))
388 (end (point))
381 (pattern (buffer-substring-no-properties beg end))
389 (pattern (buffer-substring-no-properties beg end))
382 (completions nil)
390 (completions nil)
383 (completion-table nil)
391 (completion-table nil)
384 completion
392 completion
385 (comint-preoutput-filter-functions
393 (comint-preoutput-filter-functions
386 (append comint-preoutput-filter-functions
394 (append comint-preoutput-filter-functions
387 '(ansi-color-filter-apply
395 '(ansi-color-filter-apply
388 (lambda (string)
396 (lambda (string)
389 (setq ugly-return (concat ugly-return string))
397 (setq ugly-return (concat ugly-return string))
390 "")))))
398 "")))))
391 (process-send-string python-process
399 (process-send-string python-process
392 (format ipython-completion-command-string pattern))
400 (format ipython-completion-command-string pattern))
393 (accept-process-output python-process)
401 (accept-process-output python-process)
394 (setq completions
402 (setq completions
395 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
403 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
396 ;(message (format "DEBUG completions: %S" completions))
404 ;(message (format "DEBUG completions: %S" completions))
397 (setq completion-table (loop for str in completions
405 (setq completion-table (loop for str in completions
398 collect (list str nil)))
406 collect (list str nil)))
399 (setq completion (try-completion pattern completion-table))
407 (setq completion (try-completion pattern completion-table))
400 (cond ((eq completion t))
408 (cond ((eq completion t))
401 ((null completion)
409 ((null completion)
402 (message "Can't find completion for \"%s\"" pattern)
410 (message "Can't find completion for \"%s\"" pattern)
403 (ding))
411 (ding))
404 ((not (string= pattern completion))
412 ((not (string= pattern completion))
405 (delete-region beg end)
413 (delete-region beg end)
406 (insert completion))
414 (insert completion))
407 (t
415 (t
408 (message "Making completion list...")
416 (message "Making completion list...")
409 (with-output-to-temp-buffer "*IPython Completions*"
417 (with-output-to-temp-buffer "*IPython Completions*"
410 (display-completion-list (all-completions pattern completion-table)))
418 (display-completion-list (all-completions pattern completion-table)))
411 (message "Making completion list...%s" "done")))))
419 (message "Making completion list...%s" "done")))))
412 )
420 )
413
421
414 ;;; autoindent support: patch sent in by Jin Liu <m.liu.jin@gmail.com>,
422 ;;; autoindent support: patch sent in by Jin Liu <m.liu.jin@gmail.com>,
415 ;;; originally written by doxgen@newsmth.net
423 ;;; originally written by doxgen@newsmth.net
416 ;;; Minor modifications by fperez for xemacs compatibility.
424 ;;; Minor modifications by fperez for xemacs compatibility.
417
425
418 (defvar ipython-autoindent t
426 (defvar ipython-autoindent t
419 "If non-nil, enable autoindent for IPython shell through python-mode.")
427 "If non-nil, enable autoindent for IPython shell through python-mode.")
420
428
421 (defvar ipython-indenting-buffer-name "*IPython Indentation Calculation*"
429 (defvar ipython-indenting-buffer-name "*IPython Indentation Calculation*"
422 "Temporary buffer for indenting multiline statement.")
430 "Temporary buffer for indenting multiline statement.")
423
431
424 (defun ipython-get-indenting-buffer ()
432 (defun ipython-get-indenting-buffer ()
425 "Return a temporary buffer set in python-mode. Create one if necessary."
433 "Return a temporary buffer set in python-mode. Create one if necessary."
426 (let ((buf (get-buffer-create ipython-indenting-buffer-name)))
434 (let ((buf (get-buffer-create ipython-indenting-buffer-name)))
427 (set-buffer buf)
435 (set-buffer buf)
428 (unless (eq major-mode 'python-mode)
436 (unless (eq major-mode 'python-mode)
429 (python-mode))
437 (python-mode))
430 buf))
438 buf))
431
439
432 (defvar ipython-indentation-string nil
440 (defvar ipython-indentation-string nil
433 "Indentation for the next line in a multiline statement.")
441 "Indentation for the next line in a multiline statement.")
434
442
435 (defun ipython-send-and-indent ()
443 (defun ipython-send-and-indent ()
436 "Send the current line to IPython, and calculate the indentation for
444 "Send the current line to IPython, and calculate the indentation for
437 the next line."
445 the next line."
438 (interactive)
446 (interactive)
439 (if ipython-autoindent
447 (if ipython-autoindent
440 (let ((line (buffer-substring (point-at-bol) (point)))
448 (let ((line (buffer-substring (point-at-bol) (point)))
441 (after-prompt1)
449 (after-prompt1)
442 (after-prompt2))
450 (after-prompt2))
443 (save-excursion
451 (save-excursion
444 (comint-bol t)
452 (comint-bol t)
445 (if (looking-at py-shell-input-prompt-1-regexp)
453 (if (looking-at py-shell-input-prompt-1-regexp)
446 (setq after-prompt1 t)
454 (setq after-prompt1 t)
447 (setq after-prompt2 (looking-at py-shell-input-prompt-2-regexp)))
455 (setq after-prompt2 (looking-at py-shell-input-prompt-2-regexp)))
448 (with-current-buffer (ipython-get-indenting-buffer)
456 (with-current-buffer (ipython-get-indenting-buffer)
449 (when after-prompt1
457 (when after-prompt1
450 (erase-buffer))
458 (erase-buffer))
451 (when (or after-prompt1 after-prompt2)
459 (when (or after-prompt1 after-prompt2)
452 (delete-region (point-at-bol) (point))
460 (delete-region (point-at-bol) (point))
453 (insert line)
461 (insert line)
454 (newline-and-indent))))))
462 (newline-and-indent))))))
455 ;; send input line to ipython interpreter
463 ;; send input line to ipython interpreter
456 (comint-send-input))
464 (comint-send-input))
457
465
458 (defun ipython-indentation-hook (string)
466 (defun ipython-indentation-hook (string)
459 "Insert indentation string if py-shell-input-prompt-2-regexp
467 "Insert indentation string if py-shell-input-prompt-2-regexp
460 matches last process output."
468 matches last process output."
461 (let* ((start-marker (or comint-last-output-start
469 (let* ((start-marker (or comint-last-output-start
462 (point-min-marker)))
470 (point-min-marker)))
463 (end-marker (process-mark (get-buffer-process (current-buffer))))
471 (end-marker (process-mark (get-buffer-process (current-buffer))))
464 (text (ansi-color-filter-apply (buffer-substring start-marker end-marker))))
472 (text (ansi-color-filter-apply (buffer-substring start-marker end-marker))))
465 ;; XXX if `text' matches both pattern, it MUST be the last prompt-2
473 ;; XXX if `text' matches both pattern, it MUST be the last prompt-2
466 (when (and (string-match py-shell-input-prompt-2-regexp text)
474 (when (and (string-match py-shell-input-prompt-2-regexp text)
467 (not (string-match "\n$" text)))
475 (not (string-match "\n$" text)))
468 (with-current-buffer (ipython-get-indenting-buffer)
476 (with-current-buffer (ipython-get-indenting-buffer)
469 (setq ipython-indentation-string
477 (setq ipython-indentation-string
470 (buffer-substring (point-at-bol) (point))))
478 (buffer-substring (point-at-bol) (point))))
471 (goto-char end-marker)
479 (goto-char end-marker)
472 (insert ipython-indentation-string)
480 (insert ipython-indentation-string)
473 (setq ipython-indentation-string nil))))
481 (setq ipython-indentation-string nil))))
474
482
475 (add-hook 'py-shell-hook
483 (add-hook 'py-shell-hook
476 (lambda ()
484 (lambda ()
477 (add-hook 'comint-output-filter-functions
485 (add-hook 'comint-output-filter-functions
478 'ipython-indentation-hook)))
486 'ipython-indentation-hook)))
479
487
480 (define-key py-shell-map (kbd "RET") 'ipython-send-and-indent)
488 (define-key py-shell-map (kbd "RET") 'ipython-send-and-indent)
481 ;;; / end autoindent support
489 ;;; / end autoindent support
482
490
483 (provide 'ipython)
491 (provide 'ipython)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
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