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

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

@@ -0,0 +1,256 b''
1 // !$*UTF8*$!
2 {
3 archiveVersion = 1;
4 classes = {
5 };
6 objectVersion = 42;
7 objects = {
8
9 /* Begin PBXContainerItemProxy section */
10 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */ = {
11 isa = PBXContainerItemProxy;
12 containerPortal = 4C96F4FE0E199AB500B03430 /* Project object */;
13 proxyType = 1;
14 remoteGlobalIDString = 4C96F50C0E199AF100B03430;
15 remoteInfo = "Cocoa Frontend Plugin";
16 };
17 /* End PBXContainerItemProxy section */
18
19 /* Begin PBXFileReference section */
20 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Plugin-Info.plist"; sourceTree = "<group>"; };
21 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Placeholder (Do Not Use).bundle"; sourceTree = BUILT_PRODUCTS_DIR; };
22 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Placeholder (Do Not Use)-Info.plist"; sourceTree = "<group>"; };
23 /* End PBXFileReference section */
24
25 /* Begin PBXFrameworksBuildPhase section */
26 4C5B7AD10E1A0BC8006CB905 /* Frameworks */ = {
27 isa = PBXFrameworksBuildPhase;
28 buildActionMask = 2147483647;
29 files = (
30 );
31 runOnlyForDeploymentPostprocessing = 0;
32 };
33 /* End PBXFrameworksBuildPhase section */
34
35 /* Begin PBXGroup section */
36 4C5B7A8C0E1A0B4C006CB905 /* Products */ = {
37 isa = PBXGroup;
38 children = (
39 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */,
40 );
41 name = Products;
42 sourceTree = "<group>";
43 };
44 4C96F4FC0E199AB500B03430 = {
45 isa = PBXGroup;
46 children = (
47 4C5B7A8C0E1A0B4C006CB905 /* Products */,
48 4C5B7A8D0E1A0B4C006CB905 /* Plugin-Info.plist */,
49 4C5B7AD40E1A0BC8006CB905 /* Placeholder (Do Not Use)-Info.plist */,
50 );
51 sourceTree = "<group>";
52 };
53 /* End PBXGroup section */
54
55 /* Begin PBXLegacyTarget section */
56 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */ = {
57 isa = PBXLegacyTarget;
58 buildArgumentsString = "$(ACTION)";
59 buildConfigurationList = 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */;
60 buildPhases = (
61 );
62 buildToolPath = /usr/bin/make;
63 buildWorkingDirectory = "";
64 dependencies = (
65 );
66 name = "Cocoa Frontend Plugin";
67 passBuildSettingsInEnvironment = 1;
68 productName = "Cocoa Frontend Plugin";
69 };
70 /* End PBXLegacyTarget section */
71
72 /* Begin PBXNativeTarget section */
73 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */ = {
74 isa = PBXNativeTarget;
75 buildConfigurationList = 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */;
76 buildPhases = (
77 4C5B7ACF0E1A0BC8006CB905 /* Resources */,
78 4C5B7AD00E1A0BC8006CB905 /* Sources */,
79 4C5B7AD10E1A0BC8006CB905 /* Frameworks */,
80 );
81 buildRules = (
82 );
83 dependencies = (
84 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */,
85 );
86 name = "Placeholder (Do Not Use)";
87 productName = "Placeholder (Do Not Use)";
88 productReference = 4C5B7AD30E1A0BC8006CB905 /* Placeholder (Do Not Use).bundle */;
89 productType = "com.apple.product-type.bundle";
90 };
91 /* End PBXNativeTarget section */
92
93 /* Begin PBXProject section */
94 4C96F4FE0E199AB500B03430 /* Project object */ = {
95 isa = PBXProject;
96 buildConfigurationList = 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */;
97 compatibilityVersion = "Xcode 2.4";
98 hasScannedForEncodings = 0;
99 mainGroup = 4C96F4FC0E199AB500B03430;
100 productRefGroup = 4C5B7A8C0E1A0B4C006CB905 /* Products */;
101 projectDirPath = "";
102 projectRoot = "";
103 targets = (
104 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */,
105 4C5B7AD20E1A0BC8006CB905 /* Placeholder (Do Not Use) */,
106 );
107 };
108 /* End PBXProject section */
109
110 /* Begin PBXResourcesBuildPhase section */
111 4C5B7ACF0E1A0BC8006CB905 /* Resources */ = {
112 isa = PBXResourcesBuildPhase;
113 buildActionMask = 2147483647;
114 files = (
115 );
116 runOnlyForDeploymentPostprocessing = 0;
117 };
118 /* End PBXResourcesBuildPhase section */
119
120 /* Begin PBXSourcesBuildPhase section */
121 4C5B7AD00E1A0BC8006CB905 /* Sources */ = {
122 isa = PBXSourcesBuildPhase;
123 buildActionMask = 2147483647;
124 files = (
125 );
126 runOnlyForDeploymentPostprocessing = 0;
127 };
128 /* End PBXSourcesBuildPhase section */
129
130 /* Begin PBXTargetDependency section */
131 4C5B7ADC0E1A0BCD006CB905 /* PBXTargetDependency */ = {
132 isa = PBXTargetDependency;
133 target = 4C96F50C0E199AF100B03430 /* Cocoa Frontend Plugin */;
134 targetProxy = 4C5B7ADB0E1A0BCD006CB905 /* PBXContainerItemProxy */;
135 };
136 /* End PBXTargetDependency section */
137
138 /* Begin XCBuildConfiguration section */
139 4C5B7AD50E1A0BC9006CB905 /* Debug */ = {
140 isa = XCBuildConfiguration;
141 buildSettings = {
142 COPY_PHASE_STRIP = NO;
143 GCC_DYNAMIC_NO_PIC = NO;
144 GCC_ENABLE_FIX_AND_CONTINUE = YES;
145 GCC_MODEL_TUNING = G5;
146 GCC_OPTIMIZATION_LEVEL = 0;
147 GCC_PRECOMPILE_PREFIX_HEADER = YES;
148 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
149 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
150 INSTALL_PATH = "$(HOME)/Library/Bundles";
151 OTHER_LDFLAGS = (
152 "-framework",
153 Foundation,
154 "-framework",
155 AppKit,
156 );
157 PREBINDING = NO;
158 PRODUCT_NAME = "Placeholder (Do Not Use)";
159 WRAPPER_EXTENSION = bundle;
160 ZERO_LINK = YES;
161 };
162 name = Debug;
163 };
164 4C5B7AD60E1A0BC9006CB905 /* Release */ = {
165 isa = XCBuildConfiguration;
166 buildSettings = {
167 COPY_PHASE_STRIP = YES;
168 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
169 GCC_ENABLE_FIX_AND_CONTINUE = NO;
170 GCC_MODEL_TUNING = G5;
171 GCC_PRECOMPILE_PREFIX_HEADER = YES;
172 GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
173 INFOPLIST_FILE = "Placeholder (Do Not Use)-Info.plist";
174 INSTALL_PATH = "$(HOME)/Library/Bundles";
175 OTHER_LDFLAGS = (
176 "-framework",
177 Foundation,
178 "-framework",
179 AppKit,
180 );
181 PREBINDING = NO;
182 PRODUCT_NAME = "Placeholder (Do Not Use)";
183 WRAPPER_EXTENSION = bundle;
184 ZERO_LINK = NO;
185 };
186 name = Release;
187 };
188 4C96F4FF0E199AB500B03430 /* Debug */ = {
189 isa = XCBuildConfiguration;
190 buildSettings = {
191 COPY_PHASE_STRIP = NO;
192 };
193 name = Debug;
194 };
195 4C96F5000E199AB500B03430 /* Release */ = {
196 isa = XCBuildConfiguration;
197 buildSettings = {
198 COPY_PHASE_STRIP = YES;
199 };
200 name = Release;
201 };
202 4C96F50D0E199AF100B03430 /* Debug */ = {
203 isa = XCBuildConfiguration;
204 buildSettings = {
205 COPY_PHASE_STRIP = NO;
206 GCC_DYNAMIC_NO_PIC = NO;
207 GCC_OPTIMIZATION_LEVEL = 0;
208 PRODUCT_NAME = "Cocoa Frontend Plugin";
209 };
210 name = Debug;
211 };
212 4C96F50E0E199AF100B03430 /* Release */ = {
213 isa = XCBuildConfiguration;
214 buildSettings = {
215 COPY_PHASE_STRIP = YES;
216 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
217 GCC_ENABLE_FIX_AND_CONTINUE = NO;
218 PRODUCT_NAME = "Cocoa Frontend Plugin";
219 ZERO_LINK = NO;
220 };
221 name = Release;
222 };
223 /* End XCBuildConfiguration section */
224
225 /* Begin XCConfigurationList section */
226 4C5B7ADA0E1A0BC9006CB905 /* Build configuration list for PBXNativeTarget "Placeholder (Do Not Use)" */ = {
227 isa = XCConfigurationList;
228 buildConfigurations = (
229 4C5B7AD50E1A0BC9006CB905 /* Debug */,
230 4C5B7AD60E1A0BC9006CB905 /* Release */,
231 );
232 defaultConfigurationIsVisible = 0;
233 defaultConfigurationName = Release;
234 };
235 4C96F5010E199AB500B03430 /* Build configuration list for PBXProject "CocoaFrontendPlugin" */ = {
236 isa = XCConfigurationList;
237 buildConfigurations = (
238 4C96F4FF0E199AB500B03430 /* Debug */,
239 4C96F5000E199AB500B03430 /* Release */,
240 );
241 defaultConfigurationIsVisible = 0;
242 defaultConfigurationName = Release;
243 };
244 4C96F5110E199B3300B03430 /* Build configuration list for PBXLegacyTarget "Cocoa Frontend Plugin" */ = {
245 isa = XCConfigurationList;
246 buildConfigurations = (
247 4C96F50D0E199AF100B03430 /* Debug */,
248 4C96F50E0E199AF100B03430 /* Release */,
249 );
250 defaultConfigurationIsVisible = 0;
251 defaultConfigurationName = Release;
252 };
253 /* End XCConfigurationList section */
254 };
255 rootObject = 4C96F4FE0E199AB500B03430 /* Project object */;
256 }
@@ -0,0 +1,25 b''
1 # encoding: utf-8
2 """
3 Provides a namespace for loading the Cocoa frontend via a Cocoa plugin.
4
5 Author: Barry Wark
6 """
7 __docformat__ = "restructuredtext en"
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 from PyObjCTools import AppHelper
17 from twisted.internet import _threadedselect
18
19 #make sure _threadedselect is installed first
20 reactor = _threadedselect.install()
21
22 # load the Cocoa frontend controller
23 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
24 reactor.interleave(AppHelper.callAfter)
25 assert(reactor.running)
@@ -0,0 +1,6 b''
1 include ./plugins.mk
2
3 all : dist/IPythonCocoaController.plugin
4
5 dist/IPythonCocoaController.plugin : ./IPythonCocoaFrontendLoader.py\
6 ./setup.py No newline at end of file
@@ -0,0 +1,20 b''
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 <plist version="1.0">
4 <dict>
5 <key>CFBundleDevelopmentRegion</key>
6 <string>English</string>
7 <key>CFBundleExecutable</key>
8 <string>${EXECUTABLE_NAME}</string>
9 <key>CFBundleIdentifier</key>
10 <string>com.yourcompany.Placeholder (Do Not Use)</string>
11 <key>CFBundleInfoDictionaryVersion</key>
12 <string>6.0</string>
13 <key>CFBundlePackageType</key>
14 <string>BNDL</string>
15 <key>CFBundleSignature</key>
16 <string>????</string>
17 <key>CFBundleVersion</key>
18 <string>1.0</string>
19 </dict>
20 </plist>
@@ -0,0 +1,21 b''
1 %.plugin::
2 rm -rf dist/$(notdir $@)
3 rm -rf build dist && \
4 python setup.py py2app -s
5
6 %.py:
7 @echo "test -f $@"
8 @test -f %@
9
10 %.nib:
11 @echo "test -f $@"
12 @test -f %@
13
14 .DEFAULT_GOAL := all
15
16 .PHONY : all clean
17
18 clean :
19 rm -rf build dist
20
21
@@ -0,0 +1,35 b''
1 # encoding: utf-8
2 """
3 setup.py
4
5 Setuptools installer script for generating a Cocoa plugin for the
6 IPython cocoa frontend
7
8 Author: Barry Wark
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 from setuptools import setup
20
21 infoPlist = dict(
22 CFBundleDevelopmentRegion='English',
23 CFBundleIdentifier='org.scipy.ipython.cocoa_frontend',
24 NSPrincipalClass='IPythonCocoaController',
25 )
26
27 setup(
28 plugin=['IPythonCocoaFrontendLoader.py'],
29 setup_requires=['py2app'],
30 options=dict(py2app=dict(
31 plist=infoPlist,
32 site_packages=True,
33 excludes=['IPython','twisted','PyObjCTools']
34 )),
35 ) No newline at end of file
@@ -0,0 +1,20 b''
1 # Set this prefix to where you want to install the plugin
2 PREFIX=~/usr/local
3 PREFIX=~/tmp/local
4
5 plugin: IPython_doctest_plugin.egg-info
6
7 test: plugin dtexample.py
8 nosetests -s --with-ipdoctest --doctest-tests --doctest-extension=txt \
9 dtexample.py test*.txt
10
11 deb: plugin dtexample.py
12 nosetests -vs --with-ipdoctest --doctest-tests --doctest-extension=txt \
13 test_combo.txt
14
15 IPython_doctest_plugin.egg-info: ipdoctest.py setup.py
16 python setup.py install --prefix=$(PREFIX)
17 touch $@
18
19 clean:
20 rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/
@@ -0,0 +1,39 b''
1 =======================================================
2 Nose plugin with IPython and extension module support
3 =======================================================
4
5 This directory provides the key functionality for test support that IPython
6 needs as a nose plugin, which can be installed for use in projects other than
7 IPython.
8
9 The presence of a Makefile here is mostly for development and debugging
10 purposes as it only provides a few shorthand commands. You can manually
11 install the plugin by using standard Python procedures (``setup.py install``
12 with appropriate arguments).
13
14 To install the plugin using the Makefile, edit its first line to reflect where
15 you'd like the installation. If you want it system-wide, you may want to edit
16 the install line in the plugin target to use sudo and no prefix::
17
18 sudo python setup.py install
19
20 instead of the code using `--prefix` that's in there.
21
22 Once you've set the prefix, simply build/install the plugin with::
23
24 make
25
26 and run the tests with::
27
28 make test
29
30 You should see output similar to::
31
32 maqroll[plugin]> make test
33 nosetests -s --with-ipdoctest --doctest-tests dtexample.py
34 ..
35 ----------------------------------------------------------------------
36 Ran 2 tests in 0.016s
37
38 OK
39
@@ -0,0 +1,72 b''
1 """Simple example using doctests.
2
3 This file just contains doctests both using plain python and IPython prompts.
4 All tests should be loaded by nose.
5 """
6
7 def pyfunc():
8 """Some pure python tests...
9
10 >>> pyfunc()
11 'pyfunc'
12
13 >>> import os
14
15 >>> 2+3
16 5
17
18 >>> for i in range(3):
19 ... print i,
20 ... print i+1,
21 ...
22 0 1 1 2 2 3
23 """
24
25 return 'pyfunc'
26
27 def ipfunc():
28 """Some ipython tests...
29
30 In [1]: import os
31
32 In [2]: cd /
33 /
34
35 In [3]: 2+3
36 Out[3]: 5
37
38 In [26]: for i in range(3):
39 ....: print i,
40 ....: print i+1,
41 ....:
42 0 1 1 2 2 3
43
44
45 Examples that access the operating system work:
46
47 In [1]: !echo hello
48 hello
49
50 In [2]: !echo hello > /tmp/foo
51
52 In [3]: !cat /tmp/foo
53 hello
54
55 In [4]: rm -f /tmp/foo
56
57 It's OK to use '_' for the last result, but do NOT try to use IPython's
58 numbered history of _NN outputs, since those won't exist under the
59 doctest environment:
60
61 In [7]: 3+4
62 Out[7]: 7
63
64 In [8]: _+3
65 Out[8]: 10
66
67 In [9]: ipfunc()
68 Out[9]: 'ipfunc'
69 """
70
71 return 'ipfunc'
72
This diff has been collapsed as it changes many lines, (587 lines changed) Show them Hide them
@@ -0,0 +1,587 b''
1 """Nose Plugin that supports IPython doctests.
2
3 Limitations:
4
5 - When generating examples for use as doctests, make sure that you have
6 pretty-printing OFF. This can be done either by starting ipython with the
7 flag '--nopprint', by setting pprint to 0 in your ipythonrc file, or by
8 interactively disabling it with %Pprint. This is required so that IPython
9 output matches that of normal Python, which is used by doctest for internal
10 execution.
11
12 - Do not rely on specific prompt numbers for results (such as using
13 '_34==True', for example). For IPython tests run via an external process the
14 prompt numbers may be different, and IPython tests run as normal python code
15 won't even have these special _NN variables set at all.
16
17 - IPython functions that produce output as a side-effect of calling a system
18 process (e.g. 'ls') can be doc-tested, but they must be handled in an
19 external IPython process. Such doctests must be tagged with:
20
21 # ipdoctest: EXTERNAL
22
23 so that the testing machinery handles them differently. Since these are run
24 via pexpect in an external process, they can't deal with exceptions or other
25 fancy featurs of regular doctests. You must limit such tests to simple
26 matching of the output. For this reason, I recommend you limit these kinds
27 of doctests to features that truly require a separate process, and use the
28 normal IPython ones (which have all the features of normal doctests) for
29 everything else. See the examples at the bottom of this file for a
30 comparison of what can be done with both types.
31 """
32
33
34 #-----------------------------------------------------------------------------
35 # Module imports
36
37 # From the standard library
38 import __builtin__
39 import commands
40 import doctest
41 import inspect
42 import logging
43 import os
44 import re
45 import sys
46 import unittest
47
48 from inspect import getmodule
49
50 # Third-party modules
51 import nose.core
52
53 from nose.plugins import doctests, Plugin
54 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
55
56 # Our own imports
57 #from extdoctest import ExtensionDoctest, DocTestFinder
58 #from dttools import DocTestFinder, DocTestCase
59 #-----------------------------------------------------------------------------
60 # Module globals and other constants
61
62 log = logging.getLogger(__name__)
63
64 ###########################################################################
65 # *** HACK ***
66 # We must start our own ipython object and heavily muck with it so that all the
67 # modifications IPython makes to system behavior don't send the doctest
68 # machinery into a fit. This code should be considered a gross hack, but it
69 # gets the job done.
70
71 def start_ipython():
72 """Start a global IPython shell, which we need for IPython-specific syntax.
73 """
74 import IPython
75
76 def xsys(cmd):
77 """Execute a command and print its output.
78
79 This is just a convenience function to replace the IPython system call
80 with one that is more doctest-friendly.
81 """
82 cmd = _ip.IP.var_expand(cmd,depth=1)
83 sys.stdout.write(commands.getoutput(cmd))
84 sys.stdout.flush()
85
86 # Store certain global objects that IPython modifies
87 _displayhook = sys.displayhook
88 _excepthook = sys.excepthook
89 _main = sys.modules.get('__main__')
90
91 # Start IPython instance
92 IPython.Shell.IPShell(['--classic','--noterm_title'])
93
94 # Deactivate the various python system hooks added by ipython for
95 # interactive convenience so we don't confuse the doctest system
96 sys.modules['__main__'] = _main
97 sys.displayhook = _displayhook
98 sys.excepthook = _excepthook
99
100 # So that ipython magics and aliases can be doctested (they work by making
101 # a call into a global _ip object)
102 _ip = IPython.ipapi.get()
103 __builtin__._ip = _ip
104
105 # Modify the IPython system call with one that uses getoutput, so that we
106 # can capture subcommands and print them to Python's stdout, otherwise the
107 # doctest machinery would miss them.
108 _ip.system = xsys
109
110 # The start call MUST be made here. I'm not sure yet why it doesn't work if
111 # it is made later, at plugin initialization time, but in all my tests, that's
112 # the case.
113 start_ipython()
114
115 # *** END HACK ***
116 ###########################################################################
117
118 #-----------------------------------------------------------------------------
119 # Modified version of the one in the stdlib, that fixes a python bug (doctests
120 # not found in extension modules, http://bugs.python.org/issue3158)
121 class DocTestFinder(doctest.DocTestFinder):
122
123 def _from_module(self, module, object):
124 """
125 Return true if the given object is defined in the given
126 module.
127 """
128 if module is None:
129 #print '_fm C1' # dbg
130 return True
131 elif inspect.isfunction(object):
132 #print '_fm C2' # dbg
133 return module.__dict__ is object.func_globals
134 elif inspect.isbuiltin(object):
135 #print '_fm C2-1' # dbg
136 return module.__name__ == object.__module__
137 elif inspect.isclass(object):
138 #print '_fm C3' # dbg
139 return module.__name__ == object.__module__
140 elif inspect.ismethod(object):
141 # This one may be a bug in cython that fails to correctly set the
142 # __module__ attribute of methods, but since the same error is easy
143 # to make by extension code writers, having this safety in place
144 # isn't such a bad idea
145 #print '_fm C3-1' # dbg
146 return module.__name__ == object.im_class.__module__
147 elif inspect.getmodule(object) is not None:
148 #print '_fm C4' # dbg
149 #print 'C4 mod',module,'obj',object # dbg
150 return module is inspect.getmodule(object)
151 elif hasattr(object, '__module__'):
152 #print '_fm C5' # dbg
153 return module.__name__ == object.__module__
154 elif isinstance(object, property):
155 #print '_fm C6' # dbg
156 return True # [XX] no way not be sure.
157 else:
158 raise ValueError("object must be a class or function")
159
160
161
162 def _find(self, tests, obj, name, module, source_lines, globs, seen):
163 """
164 Find tests for the given object and any contained objects, and
165 add them to `tests`.
166 """
167
168 doctest.DocTestFinder._find(self,tests, obj, name, module,
169 source_lines, globs, seen)
170
171 # Below we re-run pieces of the above method with manual modifications,
172 # because the original code is buggy and fails to correctly identify
173 # doctests in extension modules.
174
175 # Local shorthands
176 from inspect import isroutine, isclass, ismodule
177
178 # Look for tests in a module's contained objects.
179 if inspect.ismodule(obj) and self._recurse:
180 for valname, val in obj.__dict__.items():
181 valname1 = '%s.%s' % (name, valname)
182 if ( (isroutine(val) or isclass(val))
183 and self._from_module(module, val) ):
184
185 self._find(tests, val, valname1, module, source_lines,
186 globs, seen)
187
188
189 # Look for tests in a class's contained objects.
190 if inspect.isclass(obj) and self._recurse:
191 #print 'RECURSE into class:',obj # dbg
192 for valname, val in obj.__dict__.items():
193 #valname1 = '%s.%s' % (name, valname) # dbg
194 #print 'N',name,'VN:',valname,'val:',str(val)[:77] # dbg
195 # Special handling for staticmethod/classmethod.
196 if isinstance(val, staticmethod):
197 val = getattr(obj, valname)
198 if isinstance(val, classmethod):
199 val = getattr(obj, valname).im_func
200
201 # Recurse to methods, properties, and nested classes.
202 if ((inspect.isfunction(val) or inspect.isclass(val) or
203 inspect.ismethod(val) or
204 isinstance(val, property)) and
205 self._from_module(module, val)):
206 valname = '%s.%s' % (name, valname)
207 self._find(tests, val, valname, module, source_lines,
208 globs, seen)
209
210
211 class DocTestCase(doctests.DocTestCase):
212 """Proxy for DocTestCase: provides an address() method that
213 returns the correct address for the doctest case. Otherwise
214 acts as a proxy to the test case. To provide hints for address(),
215 an obj may also be passed -- this will be used as the test object
216 for purposes of determining the test address, if it is provided.
217 """
218
219 # doctests loaded via find(obj) omit the module name
220 # so we need to override id, __repr__ and shortDescription
221 # bonus: this will squash a 2.3 vs 2.4 incompatiblity
222 def id(self):
223 name = self._dt_test.name
224 filename = self._dt_test.filename
225 if filename is not None:
226 pk = getpackage(filename)
227 if pk is not None and not name.startswith(pk):
228 name = "%s.%s" % (pk, name)
229 return name
230
231
232 # Classes and functions
233
234 def is_extension_module(filename):
235 """Return whether the given filename is an extension module.
236
237 This simply checks that the extension is either .so or .pyd.
238 """
239 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
240
241
242 # A simple subclassing of the original with a different class name, so we can
243 # distinguish and treat differently IPython examples from pure python ones.
244 class IPExample(doctest.Example): pass
245
246 class IPExternalExample(doctest.Example):
247 """Doctest examples to be run in an external process."""
248
249 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
250 options=None):
251 # Parent constructor
252 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
253
254 # An EXTRA newline is needed to prevent pexpect hangs
255 self.source += '\n'
256
257 class IPDocTestParser(doctest.DocTestParser):
258 """
259 A class used to parse strings containing doctest examples.
260
261 Note: This is a version modified to properly recognize IPython input and
262 convert any IPython examples into valid Python ones.
263 """
264 # This regular expression is used to find doctest examples in a
265 # string. It defines three groups: `source` is the source code
266 # (including leading indentation and prompts); `indent` is the
267 # indentation of the first (PS1) line of the source code; and
268 # `want` is the expected output (including leading indentation).
269
270 # Classic Python prompts or default IPython ones
271 _PS1_PY = r'>>>'
272 _PS2_PY = r'\.\.\.'
273
274 _PS1_IP = r'In\ \[\d+\]:'
275 _PS2_IP = r'\ \ \ \.\.\.+:'
276
277 _RE_TPL = r'''
278 # Source consists of a PS1 line followed by zero or more PS2 lines.
279 (?P<source>
280 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
281 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
282 \n? # a newline
283 # Want consists of any non-blank lines that do not start with PS1.
284 (?P<want> (?:(?![ ]*$) # Not a blank line
285 (?![ ]*%s) # Not a line starting with PS1
286 (?![ ]*%s) # Not a line starting with PS2
287 .*$\n? # But any other line
288 )*)
289 '''
290
291 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
292 re.MULTILINE | re.VERBOSE)
293
294 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
295 re.MULTILINE | re.VERBOSE)
296
297 def ip2py(self,source):
298 """Convert input IPython source into valid Python."""
299 out = []
300 newline = out.append
301 for line in source.splitlines():
302 #newline(_ip.IPipython.prefilter(line,True))
303 newline(_ip.IP.prefilter(line,True))
304 newline('') # ensure a closing newline, needed by doctest
305 return '\n'.join(out)
306
307 def parse(self, string, name='<string>'):
308 """
309 Divide the given string into examples and intervening text,
310 and return them as a list of alternating Examples and strings.
311 Line numbers for the Examples are 0-based. The optional
312 argument `name` is a name identifying this string, and is only
313 used for error messages.
314 """
315
316 #print 'Parse string:\n',string # dbg
317
318 string = string.expandtabs()
319 # If all lines begin with the same indentation, then strip it.
320 min_indent = self._min_indent(string)
321 if min_indent > 0:
322 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
323
324 output = []
325 charno, lineno = 0, 0
326
327 # Whether to convert the input from ipython to python syntax
328 ip2py = False
329 # Find all doctest examples in the string. First, try them as Python
330 # examples, then as IPython ones
331 terms = list(self._EXAMPLE_RE_PY.finditer(string))
332 if terms:
333 # Normal Python example
334 #print '-'*70 # dbg
335 #print 'PyExample, Source:\n',string # dbg
336 #print '-'*70 # dbg
337 Example = doctest.Example
338 else:
339 # It's an ipython example. Note that IPExamples are run
340 # in-process, so their syntax must be turned into valid python.
341 # IPExternalExamples are run out-of-process (via pexpect) so they
342 # don't need any filtering (a real ipython will be executing them).
343 terms = list(self._EXAMPLE_RE_IP.finditer(string))
344 if re.search(r'#\s*ipdoctest:\s*EXTERNAL',string):
345 #print '-'*70 # dbg
346 #print 'IPExternalExample, Source:\n',string # dbg
347 #print '-'*70 # dbg
348 Example = IPExternalExample
349 else:
350 #print '-'*70 # dbg
351 #print 'IPExample, Source:\n',string # dbg
352 #print '-'*70 # dbg
353 Example = IPExample
354 ip2py = True
355
356 for m in terms:
357 # Add the pre-example text to `output`.
358 output.append(string[charno:m.start()])
359 # Update lineno (lines before this example)
360 lineno += string.count('\n', charno, m.start())
361 # Extract info from the regexp match.
362 (source, options, want, exc_msg) = \
363 self._parse_example(m, name, lineno,ip2py)
364 if Example is IPExternalExample:
365 options[doctest.NORMALIZE_WHITESPACE] = True
366 want += '\n'
367 # Create an Example, and add it to the list.
368 if not self._IS_BLANK_OR_COMMENT(source):
369 #print 'Example source:', source # dbg
370 output.append(Example(source, want, exc_msg,
371 lineno=lineno,
372 indent=min_indent+len(m.group('indent')),
373 options=options))
374 # Update lineno (lines inside this example)
375 lineno += string.count('\n', m.start(), m.end())
376 # Update charno.
377 charno = m.end()
378 # Add any remaining post-example text to `output`.
379 output.append(string[charno:])
380
381 return output
382
383 def _parse_example(self, m, name, lineno,ip2py=False):
384 """
385 Given a regular expression match from `_EXAMPLE_RE` (`m`),
386 return a pair `(source, want)`, where `source` is the matched
387 example's source code (with prompts and indentation stripped);
388 and `want` is the example's expected output (with indentation
389 stripped).
390
391 `name` is the string's name, and `lineno` is the line number
392 where the example starts; both are used for error messages.
393
394 Optional:
395 `ip2py`: if true, filter the input via IPython to convert the syntax
396 into valid python.
397 """
398
399 # Get the example's indentation level.
400 indent = len(m.group('indent'))
401
402 # Divide source into lines; check that they're properly
403 # indented; and then strip their indentation & prompts.
404 source_lines = m.group('source').split('\n')
405
406 # We're using variable-length input prompts
407 ps1 = m.group('ps1')
408 ps2 = m.group('ps2')
409 ps1_len = len(ps1)
410
411 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
412 if ps2:
413 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
414
415 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
416
417 if ip2py:
418 # Convert source input from IPython into valid Python syntax
419 source = self.ip2py(source)
420
421 # Divide want into lines; check that it's properly indented; and
422 # then strip the indentation. Spaces before the last newline should
423 # be preserved, so plain rstrip() isn't good enough.
424 want = m.group('want')
425 want_lines = want.split('\n')
426 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
427 del want_lines[-1] # forget final newline & spaces after it
428 self._check_prefix(want_lines, ' '*indent, name,
429 lineno + len(source_lines))
430
431 # Remove ipython output prompt that might be present in the first line
432 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
433
434 want = '\n'.join([wl[indent:] for wl in want_lines])
435
436 # If `want` contains a traceback message, then extract it.
437 m = self._EXCEPTION_RE.match(want)
438 if m:
439 exc_msg = m.group('msg')
440 else:
441 exc_msg = None
442
443 # Extract options from the source.
444 options = self._find_options(source, name, lineno)
445
446 return source, options, want, exc_msg
447
448 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
449 """
450 Given the lines of a source string (including prompts and
451 leading indentation), check to make sure that every prompt is
452 followed by a space character. If any line is not followed by
453 a space character, then raise ValueError.
454
455 Note: IPython-modified version which takes the input prompt length as a
456 parameter, so that prompts of variable length can be dealt with.
457 """
458 space_idx = indent+ps1_len
459 min_len = space_idx+1
460 for i, line in enumerate(lines):
461 if len(line) >= min_len and line[space_idx] != ' ':
462 raise ValueError('line %r of the docstring for %s '
463 'lacks blank after %s: %r' %
464 (lineno+i+1, name,
465 line[indent:space_idx], line))
466
467 SKIP = doctest.register_optionflag('SKIP')
468
469 ###########################################################################
470
471 class DocFileCase(doctest.DocFileCase):
472 """Overrides to provide filename
473 """
474 def address(self):
475 return (self._dt_test.filename, None, None)
476
477
478 class ExtensionDoctest(doctests.Doctest):
479 """Nose Plugin that supports doctests in extension modules.
480 """
481 name = 'extdoctest' # call nosetests with --with-extdoctest
482 enabled = True
483
484 def options(self, parser, env=os.environ):
485 Plugin.options(self, parser, env)
486
487 def configure(self, options, config):
488 Plugin.configure(self, options, config)
489 self.doctest_tests = options.doctest_tests
490 self.extension = tolist(options.doctestExtension)
491 self.finder = DocTestFinder()
492 self.parser = doctest.DocTestParser()
493
494
495 def loadTestsFromExtensionModule(self,filename):
496 bpath,mod = os.path.split(filename)
497 modname = os.path.splitext(mod)[0]
498 try:
499 sys.path.append(bpath)
500 module = __import__(modname)
501 tests = list(self.loadTestsFromModule(module))
502 finally:
503 sys.path.pop()
504 return tests
505
506 def loadTestsFromFile(self, filename):
507 if is_extension_module(filename):
508 for t in self.loadTestsFromExtensionModule(filename):
509 yield t
510 else:
511 ## for t in list(doctests.Doctest.loadTestsFromFile(self,filename)):
512 ## yield t
513 pass
514
515 if self.extension and anyp(filename.endswith, self.extension):
516 #print 'lTF',filename # dbg
517 name = os.path.basename(filename)
518 dh = open(filename)
519 try:
520 doc = dh.read()
521 finally:
522 dh.close()
523 test = self.parser.get_doctest(
524 doc, globs={'__file__': filename}, name=name,
525 filename=filename, lineno=0)
526 if test.examples:
527 #print 'FileCase:',test.examples # dbg
528 yield DocFileCase(test)
529 else:
530 yield False # no tests to load
531
532
533 def wantFile(self,filename):
534 """Return whether the given filename should be scanned for tests.
535
536 Modified version that accepts extension modules as valid containers for
537 doctests.
538 """
539 #print 'Filename:',filename # dbg
540
541 if is_extension_module(filename):
542 return True
543 else:
544 return doctests.Doctest.wantFile(self,filename)
545
546 # NOTE: the method below is a *copy* of the one in the nose doctests
547 # plugin, but we have to replicate it here in order to have it resolve the
548 # DocTestCase (last line) to our local copy, since the nose plugin doesn't
549 # provide a public hook for what TestCase class to use. The alternative
550 # would be to monkeypatch doctest in the stdlib, but that's ugly and
551 # brittle, since a change in plugin load order can break it. So for now,
552 # we just paste this in here, inelegant as this may be.
553
554 def loadTestsFromModule(self, module):
555 #print 'lTM',module # dbg
556
557 if not self.matches(module.__name__):
558 log.debug("Doctest doesn't want module %s", module)
559 return
560 tests = self.finder.find(module)
561 if not tests:
562 return
563 tests.sort()
564 module_file = module.__file__
565 if module_file[-4:] in ('.pyc', '.pyo'):
566 module_file = module_file[:-1]
567 for test in tests:
568 if not test.examples:
569 continue
570 if not test.filename:
571 test.filename = module_file
572 yield DocTestCase(test)
573
574 class IPythonDoctest(ExtensionDoctest):
575 """Nose Plugin that supports doctests in extension modules.
576 """
577 name = 'ipdoctest' # call nosetests with --with-ipdoctest
578 enabled = True
579
580 def configure(self, options, config):
581
582 Plugin.configure(self, options, config)
583 self.doctest_tests = options.doctest_tests
584 self.extension = tolist(options.doctestExtension)
585 self.parser = IPDocTestParser()
586 #self.finder = DocTestFinder(parser=IPDocTestParser())
587 self.finder = DocTestFinder(parser=self.parser)
@@ -0,0 +1,18 b''
1 #!/usr/bin/env python
2 """Nose-based test runner.
3 """
4
5 from nose.core import main
6 from nose.plugins.builtin import plugins
7 from nose.plugins.doctests import Doctest
8
9 import ipdoctest
10 from ipdoctest import IPDocTestRunner
11
12 if __name__ == '__main__':
13 print 'WARNING: this code is incomplete!'
14 print
15
16 pp = [x() for x in plugins] # activate all builtin plugins first
17 main(testRunner=IPDocTestRunner(),
18 plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()])
@@ -0,0 +1,18 b''
1 #!/usr/bin/env python
2 """A Nose plugin to support IPython doctests.
3 """
4
5 from setuptools import setup
6
7 setup(name='IPython doctest plugin',
8 version='0.1',
9 author='The IPython Team',
10 description = 'Nose plugin to load IPython-extended doctests',
11 license = 'LGPL',
12 py_modules = ['ipdoctest'],
13 entry_points = {
14 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest',
15 'extdoctest = ipdoctest:ExtensionDoctest',
16 ],
17 },
18 )
@@ -0,0 +1,36 b''
1 =======================
2 Combo testing example
3 =======================
4
5 This is a simple example that mixes ipython doctests::
6
7 In [1]: import code
8
9 In [2]: 2**12
10 Out[2]: 4096
11
12 with command-line example information that does *not* get executed::
13
14 $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0
15
16 and with literal examples of Python source code::
17
18 controller = dict(host='myhost',
19 engine_port=None, # default is 10105
20 control_port=None,
21 )
22
23 # keys are hostnames, values are the number of engine on that host
24 engines = dict(node1=2,
25 node2=2,
26 node3=2,
27 node3=2,
28 )
29
30 # Force failure to detect that this test is being run.
31 1/0
32
33 These source code examples are executed but no output is compared at all. An
34 error or failure is reported only if an exception is raised.
35
36 NOTE: the execution of pure python blocks is not yet working!
@@ -0,0 +1,24 b''
1 =====================================
2 Tests in example form - pure python
3 =====================================
4
5 This file contains doctest examples embedded as code blocks, using normal
6 Python prompts. See the accompanying file for similar examples using IPython
7 prompts (you can't mix both types within one file). The following will be run
8 as a test::
9
10 >>> 1+1
11 2
12 >>> print "hello"
13 hello
14
15 More than one example works::
16
17 >>> s="Hello World"
18
19 >>> s.upper()
20 'HELLO WORLD'
21
22 but you should note that the *entire* test file is considered to be a single
23 test. Individual code blocks that fail are printed separately as ``example
24 failures``, but the whole file is still counted and reported as one test.
@@ -0,0 +1,30 b''
1 =================================
2 Tests in example form - IPython
3 =================================
4
5 You can write text files with examples that use IPython prompts (as long as you
6 use the nose ipython doctest plugin), but you can not mix and match prompt
7 styles in a single file. That is, you either use all ``>>>`` prompts or all
8 IPython-style prompts. Your test suite *can* have both types, you just need to
9 put each type of example in a separate. Using IPython prompts, you can paste
10 directly from your session::
11
12 In [5]: s="Hello World"
13
14 In [6]: s.upper()
15 Out[6]: 'HELLO WORLD'
16
17 Another example::
18
19 In [8]: 1+3
20 Out[8]: 4
21
22 Just like in IPython docstrings, you can use all IPython syntax and features::
23
24 In [9]: !echo "hello"
25 hello
26
27 In [10]: a='hi'
28
29 In [11]: !echo $a
30 hi
1 NO CONTENT: new file 100644, binary diff hidden
@@ -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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
@@ -2728,8 +2728,7 b' Defaulting color scheme to \'NoColor\'"""'
2728 2728 os.chdir(os.path.expanduser(ps))
2729 2729 if self.shell.rc.term_title:
2730 2730 #print 'set term title:',self.shell.rc.term_title # dbg
2731 ttitle = 'IPy ' + abbrev_cwd()
2732 platutils.set_term_title(ttitle)
2731 platutils.set_term_title('IPy ' + abbrev_cwd())
2733 2732 except OSError:
2734 2733 print sys.exc_info()[1]
2735 2734 else:
@@ -3195,7 +3194,7 b' Defaulting color scheme to \'NoColor\'"""'
3195 3194 exec b in self.user_ns
3196 3195 self.user_ns['pasted_block'] = b
3197 3196 else:
3198 self.user_ns[par] = block
3197 self.user_ns[par] = SList(block.splitlines())
3199 3198 print "Block assigned to '%s'" % par
3200 3199
3201 3200 def magic_quickref(self,arg):
@@ -22,15 +22,19 b" name = 'ipython'"
22 22 # because bdist_rpm does not accept dashes (an RPM) convention, and
23 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 27 branch = 'ipython'
28 revision = '1016'
27 29
28 if branch == 'ipython':
29 version = '0.9.0.bzr.r' + revision
30 if development:
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 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 39 description = "Tools for interactive development in Python."
36 40
@@ -53,7 +53,7 b' globsyntax = """\\'
53 53 - readme*, exclude files ending with .bak
54 54 !.svn/ !.hg/ !*_Data/ rec:.
55 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 57 dir:foo
58 58 - the directory foo if it exists (not files in foo)
59 59 dir:*
@@ -63,13 +63,16 b' globsyntax = """\\'
63 63 foo.py is *not* included twice.
64 64 @filelist.txt
65 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 72 __version__ = "0.2"
70 73
71 74
72 import os,glob,fnmatch,sys
75 import os,glob,fnmatch,sys,re
73 76 from sets import Set as set
74 77
75 78
@@ -84,21 +87,34 b' def expand(flist,exp_dirs = False):'
84 87
85 88 """
86 89 if isinstance(flist, basestring):
87 flist = flist.split()
90 import shlex
91 flist = shlex.split(flist)
88 92 done_set = set()
89 93 denied_set = set()
90
94 cont_set = set()
95 cur_rejected_dirs = set()
96
91 97 def recfind(p, pats = ["*"]):
92 denied_dirs = ["*" + d+"*" for d in denied_set if d.endswith("/")]
93 #print "de", denied_dirs
98 denied_dirs = [os.path.dirname(d) for d in denied_set if d.endswith("/")]
94 99 for (dp,dnames,fnames) in os.walk(p):
95 100 # see if we should ignore the whole directory
96 101 dp_norm = dp.replace("\\","/") + "/"
97 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 112 #print "dp",dp
113 bname = os.path.basename(dp)
99 114 for deny_pat in denied_dirs:
100 if fnmatch.fnmatch( dp_norm, deny_pat):
115 if fnmatch.fnmatch( bname, deny_pat):
101 116 deny = True
117 cur_rejected_dirs.add(dp)
102 118 break
103 119 if deny:
104 120 continue
@@ -124,6 +140,17 b' def expand(flist,exp_dirs = False):'
124 140 if fnmatch.fnmatch(os.path.basename(p), deny_pat):
125 141 deny = True
126 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 154 if not deny:
128 155 yield it
129 156 return
@@ -158,7 +185,8 b' def expand(flist,exp_dirs = False):'
158 185 # glob only dirs
159 186 elif ent.lower().startswith('dir:'):
160 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 190 # get all files in the specified dir
163 191 elif os.path.isdir(ent) and exp_dirs:
164 192 res.extend(once_filter(filter(os.path.isfile,glob.glob(ent + os.sep+"*"))))
@@ -24,12 +24,14 b' __docformat__ = "restructuredtext en"'
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 import sys
27 28 import objc
28 29 import uuid
29 30
30 31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLocalizedString, NSIntersectionRange
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
33 35
34 36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
35 37 NSTextView, NSRulerView, NSVerticalRuler
@@ -38,7 +40,7 b' from pprint import saferepr'
38 40
39 41 import IPython
40 42 from IPython.kernel.engineservice import ThreadedEngineService
41 from IPython.frontend.frontendbase import FrontEndBase
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
42 44
43 45 from twisted.internet.threads import blockingCallFromThread
44 46 from twisted.python.failure import Failure
@@ -52,17 +54,57 b' from twisted.python.failure import Failure'
52 54 # ThreadedEngineService?
53 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 100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
60 101 waitingForEngine = objc.ivar().bool()
61 102 textView = objc.IBOutlet()
62 103
63 104 def init(self):
64 105 self = super(IPythonCocoaController, self).init()
65 FrontEndBase.__init__(self, engine=ThreadedEngineService())
106 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
66 108 if(self != None):
67 109 self._common_init()
68 110
@@ -133,13 +175,43 b' class IPythonCocoaController(NSObject, FrontEndBase):'
133 175 def execute(self, block, blockID=None):
134 176 self.waitingForEngine = True
135 177 self.willChangeValueForKey_('commandHistory')
136 d = super(IPythonCocoaController, self).execute(block, blockID)
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
137 180 d.addBoth(self._engine_done)
138 181 d.addCallback(self._update_user_ns)
139 182
140 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 215 def _engine_done(self, x):
144 216 self.waitingForEngine = False
145 217 self.didChangeValueForKey_('commandHistory')
@@ -166,14 +238,14 b' class IPythonCocoaController(NSObject, FrontEndBase):'
166 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 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 247 else:
173 blockID = result['blockID']
174
175
176 self.insert_text(self.input_prompt(result=result),
248 self.insert_text(self.input_prompt(number=result['number']),
177 249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 250 scrollToVisible=False
179 251 )
@@ -188,7 +260,7 b' class IPythonCocoaController(NSObject, FrontEndBase):'
188 260
189 261 #print inputRange,self.current_block_range()
190 262 self.insert_text('\n' +
191 self.output_prompt(result) +
263 self.output_prompt(number=result['number']) +
192 264 result.get('display',{}).get('pprint','') +
193 265 '\n\n',
194 266 textRange=NSMakeRange(inputRange.location+inputRange.length,
@@ -197,7 +269,11 b' class IPythonCocoaController(NSObject, FrontEndBase):'
197 269
198 270
199 271 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
272 self.insert_text('\n' +
273 self.output_prompt() +
274 '\n' +
275 failure.getErrorMessage() +
276 '\n\n')
201 277 self.start_new_block()
202 278 return failure
203 279
@@ -288,9 +364,13 b' class IPythonCocoaController(NSObject, FrontEndBase):'
288 364 def current_indent_string(self):
289 365 """returns string for indent or None if no indent"""
290 366
291 if(len(self.current_block()) > 0):
292 lines = self.current_block().split('\n')
293 currentIndent = len(lines[-1]) - len(lines[-1])
367 return self._indent_for_block(self.current_block())
368
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 374 if(currentIndent == 0):
295 375 currentIndent = self.tabSpaces
296 376
@@ -70,3 +70,22 b' class TestIPythonCocoaControler(DeferredTestCase):'
70 70
71 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
@@ -24,13 +24,24 b' import string'
24 24 import uuid
25 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 36 from IPython.kernel.core.history import FrontEndHistory
30 37 from IPython.kernel.core.util import Bunch
31 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 47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
@@ -43,7 +54,7 b" rc.prompt_out = r'Out [$number]: '"
43 54
44 55 ##############################################################################
45 56
46 class IFrontEndFactory(zi.Interface):
57 class IFrontEndFactory(Interface):
47 58 """Factory interface for frontends."""
48 59
49 60 def __call__(engine=None, history=None):
@@ -56,33 +67,30 b' class IFrontEndFactory(zi.Interface):'
56 67
57 68
58 69
59 class IFrontEnd(zi.Interface):
70 class IFrontEnd(Interface):
60 71 """Interface for frontends. All methods return t.i.d.Deferred"""
61 72
62 zi.Attribute("input_prompt_template", "string.Template instance\
73 Attribute("input_prompt_template", "string.Template instance\
63 74 substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
75 Attribute("output_prompt_template", "string.Template instance\
65 76 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
77 Attribute("continuation_prompt_template", "string.Template instance\
67 78 substituteable with execute result.")
68 79
69 def update_cell_prompt(self, result):
80 def update_cell_prompt(result, blockID=None):
70 81 """Subclass may override to update the input prompt for a block.
71 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 84 implementations should return result when finished.
74 85
75 NB: result is a failure if the execute returned a failre.
76 To get the blockID, you should do something like::
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
86 Result is a result dict in case of success, and a
87 twisted.python.util.failure.Failure in case of an error
81 88 """
82 89
83 90 pass
84 91
85 def render_result(self, result):
92
93 def render_result(result):
86 94 """Render the result of an execute call. Implementors may choose the
87 95 method of rendering.
88 96 For example, a notebook-style frontend might render a Chaco plot
@@ -90,6 +98,7 b' class IFrontEnd(zi.Interface):'
90 98
91 99 Parameters:
92 100 result : dict (result of IEngineBase.execute )
101 blockID = result['blockID']
93 102
94 103 Result:
95 104 Output of frontend rendering
@@ -97,22 +106,24 b' class IFrontEnd(zi.Interface):'
97 106
98 107 pass
99 108
100 def render_error(self, failure):
109 def render_error(failure):
101 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 112 implementations should return result when finished.
113
114 blockID = failure.blockID
104 115 """
105 116
106 117 pass
107 118
108 119
109 def input_prompt(result={}):
120 def input_prompt(number=''):
110 121 """Returns the input prompt by subsituting into
111 122 self.input_prompt_template
112 123 """
113 124 pass
114 125
115 def output_prompt(result):
126 def output_prompt(number=''):
116 127 """Returns the output prompt by subsituting into
117 128 self.output_prompt_template
118 129 """
@@ -159,9 +170,6 b' class FrontEndBase(object):'
159 170 - How do we handle completions?
160 171 """
161 172
162 zi.implements(IFrontEnd)
163 zi.classProvides(IFrontEndFactory)
164
165 173 history_cursor = 0
166 174
167 175 current_indent_level = 0
@@ -171,24 +179,20 b' class FrontEndBase(object):'
171 179 output_prompt_template = string.Template(rc.prompt_out)
172 180 continuation_prompt_template = string.Template(rc.prompt_in2)
173 181
174 def __init__(self, engine=None, history=None):
175 assert(engine==None or IEngineCore.providedBy(engine))
176 self.engine = IEngineCore(engine)
182 def __init__(self, shell=None, history=None):
183 self.shell = shell
177 184 if history is None:
178 185 self.history = FrontEndHistory(input_cache=[''])
179 186 else:
180 187 self.history = history
181 188
182 189
183 def input_prompt(self, result={}):
190 def input_prompt(self, number=''):
184 191 """Returns the current input prompt
185 192
186 193 It would be great to use ipython1.core.prompts.Prompt1 here
187 194 """
188
189 result.setdefault('number','')
190
191 return self.input_prompt_template.safe_substitute(result)
195 return self.input_prompt_template.safe_substitute({'number':number})
192 196
193 197
194 198 def continuation_prompt(self):
@@ -196,10 +200,10 b' class FrontEndBase(object):'
196 200
197 201 return self.continuation_prompt_template.safe_substitute()
198 202
199 def output_prompt(self, result):
203 def output_prompt(self, number=''):
200 204 """Returns the output prompt for result"""
201 205
202 return self.output_prompt_template.safe_substitute(result)
206 return self.output_prompt_template.safe_substitute({'number':number})
203 207
204 208
205 209 def is_complete(self, block):
@@ -239,7 +243,7 b' class FrontEndBase(object):'
239 243
240 244
241 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 248 Parameters:
245 249 block : {str, AST}
@@ -252,32 +256,41 b' class FrontEndBase(object):'
252 256 """
253 257
254 258 if(not self.is_complete(block)):
255 return Failure(Exception("Block is not compilable"))
259 raise Exception("Block is not compilable")
256 260
257 261 if(blockID == None):
258 262 blockID = uuid.uuid4() #random UUID
259 263
260 d = self.engine.execute(block)
261 d.addCallback(self._add_history, block=block)
262 d.addBoth(self._add_block_id, blockID)
263 d.addBoth(self.update_cell_prompt)
264 d.addCallbacks(self.render_result, errback=self.render_error)
264 try:
265 result = self.shell.execute(block)
266 except Exception,e:
267 e = self._add_block_id_for_failure(e, blockID=blockID)
268 e = self.update_cell_prompt(e, blockID=blockID)
269 e = self.render_error(e)
270 else:
271 result = self._add_block_id_for_result(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
273 result = self.render_result(result)
265 274
266 return d
275 return result
267 276
268 277
269 def _add_block_id(self, result, blockID):
278 def _add_block_id_for_result(self, result, blockID):
270 279 """Add the blockID to result or failure. Unfortunatley, we have to
271 280 treat failures differently than result dicts.
272 281 """
273 282
274 if(isinstance(result, Failure)):
275 result.blockID = blockID
276 else:
277 result['blockID'] = blockID
283 result['blockID'] = blockID
278 284
279 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 294 def _add_history(self, result, block=None):
282 295 """Add block to the history"""
283 296
@@ -313,20 +326,11 b' class FrontEndBase(object):'
313 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 330 """Subclass may override to update the input prompt for a block.
318 331 Since this method will be called as a
319 332 twisted.internet.defer.Deferred's callback, implementations should
320 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 336 return result
@@ -344,9 +348,60 b' class FrontEndBase(object):'
344 348 def render_error(self, failure):
345 349 """Subclasses must override to render the failure. Since this method
346 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 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
@@ -19,7 +19,7 b' import unittest'
19 19 from IPython.frontend import frontendbase
20 20 from IPython.kernel.engineservice import EngineService
21 21
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.AsyncFrontEndBase):
23 23 """FrontEndBase subclass for checking callbacks"""
24 24 def __init__(self, engine=None, history=None):
25 25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
@@ -28,7 +28,7 b' class FrontEndCallbackChecker(frontendbase.FrontEndBase):'
28 28 self.renderResultCalled = False
29 29 self.renderErrorCalled = False
30 30
31 def update_cell_prompt(self, result):
31 def update_cell_prompt(self, result, blockID=None):
32 32 self.updateCalled = True
33 33 return result
34 34
@@ -44,7 +44,7 b' class FrontEndCallbackChecker(frontendbase.FrontEndBase):'
44 44
45 45
46 46
47 class TestFrontendBase(unittest.TestCase):
47 class TestAsyncFrontendBase(unittest.TestCase):
48 48 def setUp(self):
49 49 """Setup the EngineService and FrontEndBase"""
50 50
@@ -53,7 +53,7 b' class TestFrontendBase(unittest.TestCase):'
53 53
54 54 def test_implements_IFrontEnd(self):
55 55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
56 frontendbase.AsyncFrontEndBase))
57 57
58 58
59 59 def test_is_complete_returns_False_for_incomplete_block(self):
@@ -1137,14 +1137,41 b' class SList(list):'
1137 1137 res.append(" ".join(lineparts))
1138 1138
1139 1139 return res
1140
1141
1142
1140 def sort(self,field= None, nums = False):
1141 """ sort by specified fields (see fields())
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 1168 def print_slist(arg):
1146 1169 """ Prettier (non-repr-like) and more informative printer for SList """
1147 print "SList (.p, .n, .l, .s, .grep(), .fields() available). Value:"
1170 print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
1171 if hasattr(arg, 'hideonce') and arg.hideonce:
1172 arg.hideonce = False
1173 return
1174
1148 1175 nlprint(arg)
1149 1176
1150 1177 print_slist = result_display.when_type(SList)(print_slist)
@@ -168,8 +168,7 b' def startMsg(control_host,control_port=10105):'
168 168 print 'For interactive use, you can make a MultiEngineClient with:'
169 169 print
170 170 print 'from IPython.kernel import client'
171 print "mec = client.MultiEngineClient((%r,%s))" % \
172 (control_host,control_port)
171 print "mec = client.MultiEngineClient()"
173 172 print
174 173 print 'You can then cleanly stop the cluster from IPython using:'
175 174 print
@@ -191,16 +190,18 b' def clusterLocal(opt,arg):'
191 190 logfile = pjoin(logdir_base,'ipcluster-')
192 191
193 192 print 'Starting controller:',
194 controller = Popen(['ipcontroller','--logfile',logfile])
193 controller = Popen(['ipcontroller','--logfile',logfile,'-x','-y'])
195 194 print 'Controller PID:',controller.pid
196 195
197 196 print 'Starting engines: ',
198 time.sleep(3)
197 time.sleep(5)
199 198
200 199 englogfile = '%s%s-' % (logfile,controller.pid)
201 200 mpi = opt.mpi
202 201 if mpi: # start with mpi - killing the engines with sigterm will not work if you do this
203 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi, '--logfile',englogfile])]
202 engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi',
203 mpi, '--logfile',englogfile])]
204 # engines = [Popen(['mpirun', '-np', str(opt.n), 'ipengine', '--mpi', mpi])]
204 205 else: # do what we would normally do
205 206 engines = [ Popen(['ipengine','--logfile',englogfile])
206 207 for i in range(opt.n) ]
@@ -58,12 +58,14 b' def start_engine():'
58 58 kernel_config = kernel_config_manager.get_config_obj()
59 59 core_config = core_config_manager.get_config_obj()
60 60
61
61 62 # Execute the mpi import statement that needs to call MPI_Init
63 global mpi
62 64 mpikey = kernel_config['mpi']['default']
63 65 mpi_import_statement = kernel_config['mpi'].get(mpikey, None)
64 66 if mpi_import_statement is not None:
65 67 try:
66 exec mpi_import_statement in locals(), globals()
68 exec mpi_import_statement in globals()
67 69 except:
68 70 mpi = None
69 71 else:
@@ -3,13 +3,8 b''
3 3
4 4 Importing this module should give you the implementations that are correct
5 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 9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
15 10 #
@@ -21,15 +16,32 b' from IPython import Release'
21 16 __author__ = '%s <%s>' % Release.authors['Ville']
22 17 __license__ = Release.license
23 18
24 import os,sys
19 import os
20 import sys
25 21
22 # Import the platform-specific implementations
26 23 if os.name == 'posix':
27 from platutils_posix import *
24 import platutils_posix as _platutils
28 25 elif sys.platform == 'win32':
29 from platutils_win32 import *
26 import platutils_win32 as _platutils
30 27 else:
31 from platutils_dummy import *
28 import platutils_dummy as _platutils
32 29 import warnings
33 30 warnings.warn("Platutils not available for platform '%s', some features may be missing" %
34 31 os.name)
35 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)
@@ -3,13 +3,8 b''
3 3
4 4 This has empty implementation of the platutils functions, used for
5 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 9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
15 10 #
@@ -21,9 +16,9 b' from IPython import Release'
21 16 __author__ = '%s <%s>' % Release.authors['Ville']
22 17 __license__ = Release.license
23 18
19 # This variable is part of the expected API of the module:
20 ignore_termtitle = True
24 21
25 def _dummy(*args,**kw):
22 def set_term_title(*args,**kw):
23 """Dummy no-op."""
26 24 pass
27
28 set_term_title = _dummy
29
@@ -3,12 +3,8 b''
3 3
4 4 Importing this module directly is not portable - rather, import platutils
5 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 9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
14 10 #
@@ -31,9 +27,6 b' def _dummy_op(*a, **b):'
31 27 def _set_term_title_xterm(title):
32 28 """ Change virtual terminal title in xterm-workalikes """
33 29
34 if ignore_termtitle:
35 return
36
37 30 sys.stdout.write('\033]%d;%s\007' % (0,title))
38 31
39 32
@@ -41,7 +34,3 b" if os.environ.get('TERM','') == 'xterm':"
41 34 set_term_title = _set_term_title_xterm
42 35 else:
43 36 set_term_title = _dummy_op
44
45 def freeze_term_title():
46 global ignore_termtitle
47 ignore_termtitle = True
@@ -3,12 +3,8 b''
3 3
4 4 Importing this module directly is not portable - rather, import platutils
5 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 9 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
14 10 #
@@ -22,35 +18,30 b' __license__ = Release.license'
22 18
23 19 import os
24 20
25 ignore_termtitle = 0
21 ignore_termtitle = False
26 22
27 23 try:
28 24 import ctypes
29 SetConsoleTitleW=ctypes.windll.kernel32.SetConsoleTitleW
30 SetConsoleTitleW.argtypes=[ctypes.c_wchar_p]
31 def _set_term_title(title):
32 """ Set terminal title using the ctypes"""
25
26 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
27 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
28
29 def set_term_title(title):
30 """Set terminal title using ctypes to access the Win32 APIs."""
33 31 SetConsoleTitleW(title)
34 32
35 33 except ImportError:
36 def _set_term_title(title):
37 """ Set terminal title using the 'title' command """
38 curr=os.getcwd()
39 os.chdir("C:") #Cannot be on network share when issuing system commands
40 ret = os.system("title " + title)
41 os.chdir(curr)
34 def set_term_title(title):
35 """Set terminal title using the 'title' command."""
36 global ignore_termtitle
37
38 try:
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 45 if ret:
43 ignore_termtitle = 1
44
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
46 # non-zero return code signals error, don't try again
47 ignore_termtitle = True
1 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
1 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
1 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
@@ -1,6 +1,20 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 18 # Required modules and packages
5 19
6 20 # Standard Python lib
@@ -36,7 +50,7 b' def test_path(path):'
36 50
37 51 This finds the correct path of the test package on disk, and prepends it
38 52 to the input path."""
39
53
40 54 return os.path.join(TEST_PATH,path)
41 55
42 56 def fullPath(startPath,files):
@@ -55,7 +69,7 b' def fullPath(startPath,files):'
55 69 One or more files.
56 70
57 71 :Examples:
58
72
59 73 >>> fullPath('/foo/bar.py',['a.txt','b.txt'])
60 74 ['/foo/a.txt', '/foo/b.txt']
61 75
@@ -66,7 +80,7 b' def fullPath(startPath,files):'
66 80 >>> fullPath('/foo','a.txt')
67 81 ['/a.txt']
68 82 """
69
83
70 84 files = utils.list_strings(files)
71 85 base = os.path.split(startPath)[0]
72 86 return [ os.path.join(base,f) for f in files ]
@@ -3,40 +3,15 b''
3 3 =========================================
4 4
5 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 8 >>> from IPython.tools import utils
23
9
24 10 # Some other tests for utils
25
11
26 12 >>> utils.marquee('Testing marquee')
27 13 '****************************** Testing marquee ******************************'
28
14
29 15 >>> utils.marquee('Another test',30,'.')
30 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.
@@ -16,7 +16,6 b' graft IPython/tools'
16 16 graft docs
17 17 exclude docs/\#*
18 18 exclude docs/man/*.1
19 exclude docs/ChangeLog.*
20 19
21 20 # There seems to be no way of excluding whole subdirectories, other than
22 21 # manually excluding all their subdirs. distutils really is horrible...
@@ -9,7 +9,7 b''
9 9 (defconst ipython-version "$Revision: 2927 $"
10 10 "VC version number.")
11 11
12 ;;; Commentary
12 ;;; Commentary
13 13 ;; This library makes all the functionality python-mode has when running with
14 14 ;; the normal python-interpreter available for ipython, too. It also enables a
15 15 ;; persistent py-shell command history across sessions (if you exit python
@@ -31,13 +31,20 b''
31 31 ;; To start an interactive ipython session run `py-shell' with ``M-x py-shell``
32 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 42 ;; NOTE: This mode is currently somewhat alpha and although I hope that it
35 43 ;; will work fine for most cases, doing certain things (like the
36 44 ;; autocompletion and a decent scheme to switch between python interpreters)
37 45 ;; properly will also require changes to ipython that will likely have to wait
38 46 ;; for a larger rewrite scheduled some time in the future.
39 ;;
40 ;; Also note that you currently NEED THE CVS VERSION OF PYTHON.EL.
47 ;;
41 48 ;;
42 49 ;; Further note that I don't know whether this runs under windows or not and
43 50 ;; that if it doesn't I can't really help much, not being afflicted myself.
@@ -46,14 +53,15 b''
46 53 ;; Hints for effective usage
47 54 ;; -------------------------
48 55 ;;
49 ;; - IMO the best feature by far of the ipython/emacs combo is how much easier it
50 ;; makes it to find and fix bugs thanks to the ``%pdb on``/ pdbtrack combo. Try
51 ;; it: first in the ipython to shell do ``%pdb on`` then do something that will
52 ;; raise an exception (FIXME nice example) -- and be amazed how easy it is to
53 ;; inspect the live objects in each stack frames and to jump to the
54 ;; corresponding sourcecode locations as you walk up and down the stack trace
55 ;; (even without ``%pdb on`` you can always use ``C-c -`` (`py-up-exception')
56 ;; to jump to the corresponding source code locations).
56 ;; - IMO the best feature by far of the ipython/emacs combo is how much easier
57 ;; it makes it to find and fix bugs thanks to the ``%pdb on or %debug``/
58 ;; pdbtrack combo. Try it: first in the ipython to shell do ``%pdb on`` then
59 ;; do something that will raise an exception (FIXME nice example), or type
60 ;; ``%debug`` after the exception has been raised. YOu'll be amazed at how
61 ;; easy it is to inspect the live objects in each stack frames and to jump to
62 ;; the corresponding sourcecode locations as you walk up and down the stack
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 66 ;; - emacs gives you much more powerful commandline editing and output searching
59 67 ;; capabilities than ipython-standalone -- isearch is your friend if you
@@ -65,9 +73,9 b''
65 73 ;; ipython, you can change ``meta p`` etc. for ``control p``)::
66 74 ;;
67 75 ;; (require 'comint)
68 ;; (define-key comint-mode-map [(meta p)]
76 ;; (define-key comint-mode-map [(meta p)]
69 77 ;; 'comint-previous-matching-input-from-input)
70 ;; (define-key comint-mode-map [(meta n)]
78 ;; (define-key comint-mode-map [(meta n)]
71 79 ;; 'comint-next-matching-input-from-input)
72 80 ;; (define-key comint-mode-map [(control meta n)]
73 81 ;; 'comint-next-input)
@@ -79,8 +87,8 b''
79 87 ;; variables comes later).
80 88 ;;
81 89 ;; Please send comments and feedback to the ipython-list
82 ;; (<ipython-user@scipy.net>) where I (a.s.) or someone else will try to
83 ;; answer them (it helps if you specify your emacs version, OS etc;
90 ;; (<ipython-user@scipy.org>) where I (a.s.) or someone else will try to
91 ;; answer them (it helps if you specify your emacs version, OS etc;
84 92 ;; familiarity with <http://www.catb.org/~esr/faqs/smart-questions.html> might
85 93 ;; speed up things further).
86 94 ;;
@@ -106,10 +114,10 b''
106 114 ;; - neither::
107 115 ;;
108 116 ;; (py-shell "-c print 'FOOBAR'")
109 ;;
117 ;;
110 118 ;; nor::
111 ;;
112 ;; (let ((py-python-command-args (append py-python-command-args
119 ;;
120 ;; (let ((py-python-command-args (append py-python-command-args
113 121 ;; '("-c" "print 'FOOBAR'"))))
114 122 ;; (py-shell))
115 123 ;;
@@ -127,7 +135,7 b''
127 135
128 136 (defcustom ipython-command "ipython"
129 137 "*Shell command used to start ipython."
130 :type 'string
138 :type 'string
131 139 :group 'python)
132 140
133 141 ;; Users can set this to nil
@@ -138,7 +146,7 b''
138 146 (defvar ipython-backup-of-py-python-command nil
139 147 "HACK")
140 148
141
149
142 150 (defvar ipython-de-input-prompt-regexp "\\(?:
143 151 In \\[[0-9]+\\]: *.*
144 152 ----+> \\(.*
@@ -181,16 +189,16 b' the second for a \'normal\' command, and the third for a multiline command.")'
181 189 ;;XXX this is really just a cheap hack, it only completes symbols in the
182 190 ;;interactive session -- useful nonetheless.
183 191 (define-key py-mode-map [(meta tab)] 'ipython-complete)
184
192
185 193 )
186 194 (add-hook 'py-shell-hook 'ipython-shell-hook)
187 195 ;; Regular expression that describes tracebacks for IPython in context and
188 ;; verbose mode.
189
196 ;; verbose mode.
197
190 198 ;;Adapt python-mode settings for ipython.
191 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 202 ;; current python-mode traceback-line-re scheme is too primitive,
195 203 ;; so it's either matching syntax errors, *or* everything else
196 204 ;; (XXX: should ask Fernando for a change)
@@ -200,20 +208,20 b' the second for a \'normal\' command, and the third for a multiline command.")'
200 208 (setq py-traceback-line-re
201 209 "\\(^[^\t >].+?\\.py\\).*\n +[0-9]+[^\00]*?\n-+> \\([0-9]+\\)+")
202 210
203
211
204 212 ;; Recognize the ipython pdb, whose prompt is 'ipdb>' or 'ipydb>'
205 213 ;;instead of '(Pdb)'
206 214 (setq py-pdbtrack-input-prompt "\n[(<]*[Ii]?[Pp]y?db[>)]+ ")
207 215 (setq pydb-pydbtrack-input-prompt "\n[(]*ipydb[>)]+ ")
208
216
209 217 (setq py-shell-input-prompt-1-regexp "^In \\[[0-9]+\\]: *"
210 218 py-shell-input-prompt-2-regexp "^ [.][.][.]+: *" )
211 219 ;; select a suitable color-scheme
212 220 (unless (member "-colors" py-python-command-args)
213 (setq py-python-command-args
214 (nconc py-python-command-args
221 (setq py-python-command-args
222 (nconc py-python-command-args
215 223 (list "-colors"
216 (cond
224 (cond
217 225 ((eq frame-background-mode 'dark)
218 226 "Linux")
219 227 ((eq frame-background-mode 'light)
@@ -253,7 +261,7 b' buffer already exists."'
253 261
254 262 (defadvice py-execute-region (around py-execute-buffer-ensure-process)
255 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 265 (let ((py-which-shell (if (and (comint-check-proc "*Python*") (not async))
258 266 py-python-command
259 267 ipython-backup-of-py-python-command)))
@@ -267,14 +275,14 b' be used in doctests. Example:'
267 275
268 276
269 277 In [1]: import sys
270
278
271 279 In [2]: sys.stdout.write 'Hi!\n'
272 280 ------> sys.stdout.write ('Hi!\n')
273 281 Hi!
274
282
275 283 In [3]: 3 + 4
276 284 Out[3]: 7
277
285
278 286 gets converted to:
279 287
280 288 >>> import sys
@@ -288,7 +296,7 b' gets converted to:'
288 296 ;(message (format "###DEBUG s:%de:%d" start end))
289 297 (save-excursion
290 298 (save-match-data
291 ;; replace ``In [3]: bla`` with ``>>> bla`` and
299 ;; replace ``In [3]: bla`` with ``>>> bla`` and
292 300 ;; ``... : bla`` with ``... bla``
293 301 (goto-char start)
294 302 (while (re-search-forward ipython-de-input-prompt-regexp end t)
@@ -302,7 +310,7 b' gets converted to:'
302 310 (while (re-search-forward ipython-de-output-prompt-regexp end t)
303 311 (replace-match "" t nil)))))
304 312
305 (defvar ipython-completion-command-string
313 (defvar ipython-completion-command-string
306 314 "print ';'.join(__IP.Completer.all_completions('%s')) #PYTHON-MODE SILENT\n"
307 315 "The string send to ipython to query for all possible completions")
308 316
@@ -332,19 +340,19 b' in the current *Python* session."'
332 340 (completion-table nil)
333 341 completion
334 342 (comint-output-filter-functions
335 (append comint-output-filter-functions
343 (append comint-output-filter-functions
336 344 '(ansi-color-filter-apply
337 (lambda (string)
345 (lambda (string)
338 346 ;(message (format "DEBUG filtering: %s" string))
339 347 (setq ugly-return (concat ugly-return string))
340 (delete-region comint-last-output-start
348 (delete-region comint-last-output-start
341 349 (process-mark (get-buffer-process (current-buffer)))))))))
342 350 ;(message (format "#DEBUG pattern: '%s'" pattern))
343 (process-send-string python-process
351 (process-send-string python-process
344 352 (format ipython-completion-command-string pattern))
345 353 (accept-process-output python-process)
346 354 ;(message (format "DEBUG return: %s" ugly-return))
347 (setq completions
355 (setq completions
348 356 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
349 357 (setq completion-table (loop for str in completions
350 358 collect (list str nil)))
@@ -376,22 +384,22 b' in the current *Python* session."'
376 384 ;; to let ipython have the complete line, so that context can be used
377 385 ;; to do things like filename completion etc.
378 386 (beg (save-excursion (skip-chars-backward "a-z0-9A-Z_./" (point-at-bol))
379 (point)))
387 (point)))
380 388 (end (point))
381 389 (pattern (buffer-substring-no-properties beg end))
382 390 (completions nil)
383 391 (completion-table nil)
384 392 completion
385 393 (comint-preoutput-filter-functions
386 (append comint-preoutput-filter-functions
394 (append comint-preoutput-filter-functions
387 395 '(ansi-color-filter-apply
388 (lambda (string)
396 (lambda (string)
389 397 (setq ugly-return (concat ugly-return string))
390 398 "")))))
391 (process-send-string python-process
399 (process-send-string python-process
392 400 (format ipython-completion-command-string pattern))
393 401 (accept-process-output python-process)
394 (setq completions
402 (setq completions
395 403 (split-string (substring ugly-return 0 (position ?\n ugly-return)) sep))
396 404 ;(message (format "DEBUG completions: %S" completions))
397 405 (setq completion-table (loop for str in completions
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now