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 | ||||
|
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 = [ |
|
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( |
|
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, |
|
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, |
|
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 |
|
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 |
|
|
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( |
|
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( |
|
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 |
|
|
73 | Attribute("input_prompt_template", "string.Template instance\ | |
63 | substituteable with execute result.") |
|
74 | substituteable with execute result.") | |
64 |
|
|
75 | Attribute("output_prompt_template", "string.Template instance\ | |
65 | substituteable with execute result.") |
|
76 | substituteable with execute result.") | |
66 |
|
|
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( |
|
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( |
|
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( |
|
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( |
|
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, |
|
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, |
|
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, |
|
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( |
|
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 |
re |
|
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 |
|
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 |
|
|
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) |
|
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 |
|
1251 | |||
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 |
|
1287 | |||
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 |
|
165 | |||
166 | print 'Your cluster is up and running.' |
|
166 | print 'Your cluster is up and running.' | |
167 |
|
167 | |||
168 | print 'For interactive use, you can make a MultiEngineClient with:' |
|
168 | print 'For interactive use, you can make a MultiEngineClient with:' | |
169 |
|
169 | |||
170 | print 'from IPython.kernel import client' |
|
170 | print 'from IPython.kernel import client' | |
171 |
print "mec = client.MultiEngineClient( |
|
171 | print "mec = client.MultiEngineClient()" | |
172 | (control_host,control_port) |
|
|||
173 |
|
172 | |||
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 |
|
174 | |||
176 | print 'mec.kill(controller=True)' |
|
175 | print 'mec.kill(controller=True)' | |
177 |
|
176 | |||
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( |
|
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', |
|
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 |
|
219 | |||
219 | print 'kill -INT',grpid |
|
220 | print 'kill -INT',grpid | |
220 |
|
221 | |||
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 |
|
253 | |||
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 |
|
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 |
|
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 |
|
|
24 | import platutils_posix as _platutils | |
28 | elif sys.platform == 'win32': |
|
25 | elif sys.platform == 'win32': | |
29 |
|
|
26 | import platutils_win32 as _platutils | |
30 | else: |
|
27 | else: | |
31 |
|
|
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 |
|
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 = |
|
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 |
|
34 | def set_term_title(title): | |
37 |
""" |
|
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 |
|
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 |
|
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 |
|
58 | ;; pdbtrack combo. Try it: first in the ipython to shell do ``%pdb on`` then | |
52 |
;; raise an exception (FIXME nice example) |
|
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. |
|
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