##// END OF EJS Templates
Merging everyones bug fixes into trunk....
Brian Granger -
r1723:df49db56 merge
parent child Browse files
Show More
@@ -0,0 +1,155 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the frontendbase module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17
18 import unittest
19
20 try:
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
24 except ImportError:
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
28 from IPython.testing.decorators import skip
29
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
31 """FrontEndBase subclass for checking callbacks"""
32 def __init__(self, engine=None, history=None):
33 super(FrontEndCallbackChecker, self).__init__(engine=engine,
34 history=history)
35 self.updateCalled = False
36 self.renderResultCalled = False
37 self.renderErrorCalled = False
38
39 def update_cell_prompt(self, result, blockID=None):
40 self.updateCalled = True
41 return result
42
43 def render_result(self, result):
44 self.renderResultCalled = True
45 return result
46
47
48 def render_error(self, failure):
49 self.renderErrorCalled = True
50 return failure
51
52
53
54
55 class TestAsyncFrontendBase(unittest.TestCase):
56 def setUp(self):
57 """Setup the EngineService and FrontEndBase"""
58
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
60
61 def test_implements_IFrontEnd(self):
62 assert(frontendbase.IFrontEnd.implementedBy(
63 AsyncFrontEndBase))
64
65 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
67
68 block = """def test(a):"""
69
70 assert(self.fb.is_complete(block) == False)
71
72 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
74
75 block = """def test(a): pass"""
76
77 assert(self.fb.is_complete(block))
78
79 block = """a=3"""
80
81 assert(self.fb.is_complete(block))
82
83 def test_blockID_added_to_result(self):
84 block = """3+3"""
85
86 d = self.fb.execute(block, blockID='TEST_ID')
87
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
89
90 def test_blockID_added_to_failure(self):
91 block = "raise Exception()"
92
93 d = self.fb.execute(block,blockID='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
95
96 def checkBlockID(self, result, expected=""):
97 assert(result['blockID'] == expected)
98
99
100 def checkFailureID(self, failure, expected=""):
101 assert(failure.blockID == expected)
102
103
104 def test_callbacks_added_to_execute(self):
105 """test that
106 update_cell_prompt
107 render_result
108
109 are added to execute request
110 """
111
112 d = self.fb.execute("10+10")
113 d.addCallback(self.checkCallbacks)
114
115 def checkCallbacks(self, result):
116 assert(self.fb.updateCalled)
117 assert(self.fb.renderResultCalled)
118
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
120 def test_error_callback_added_to_execute(self):
121 """test that render_error called on execution error"""
122
123 d = self.fb.execute("raise Exception()")
124 d.addCallback(self.checkRenderError)
125
126 def checkRenderError(self, result):
127 assert(self.fb.renderErrorCalled)
128
129 def test_history_returns_expected_block(self):
130 """Make sure history browsing doesn't fail"""
131
132 blocks = ["a=1","a=2","a=3"]
133 for b in blocks:
134 d = self.fb.execute(b)
135
136 # d is now the deferred for the last executed block
137 d.addCallback(self.historyTests, blocks)
138
139
140 def historyTests(self, result, blocks):
141 """historyTests"""
142
143 assert(len(blocks) >= 3)
144 assert(self.fb.get_history_previous("") == blocks[-2])
145 assert(self.fb.get_history_previous("") == blocks[-3])
146 assert(self.fb.get_history_next() == blocks[-2])
147
148
149 def test_history_returns_none_at_startup(self):
150 """test_history_returns_none_at_startup"""
151
152 assert(self.fb.get_history_previous("")==None)
153 assert(self.fb.get_history_next()==None)
154
155
@@ -0,0 +1,161 b''
1 """Tests for the decorators we've created for IPython.
2 """
3
4 # Module imports
5 # Std lib
6 import inspect
7 import sys
8
9 # Third party
10 import nose.tools as nt
11
12 # Our own
13 from IPython.testing import decorators as dec
14
15
16 #-----------------------------------------------------------------------------
17 # Utilities
18
19 # Note: copied from OInspect, kept here so the testing stuff doesn't create
20 # circular dependencies and is easier to reuse.
21 def getargspec(obj):
22 """Get the names and default values of a function's arguments.
23
24 A tuple of four things is returned: (args, varargs, varkw, defaults).
25 'args' is a list of the argument names (it may contain nested lists).
26 'varargs' and 'varkw' are the names of the * and ** arguments or None.
27 'defaults' is an n-tuple of the default values of the last n arguments.
28
29 Modified version of inspect.getargspec from the Python Standard
30 Library."""
31
32 if inspect.isfunction(obj):
33 func_obj = obj
34 elif inspect.ismethod(obj):
35 func_obj = obj.im_func
36 else:
37 raise TypeError, 'arg is not a Python function'
38 args, varargs, varkw = inspect.getargs(func_obj.func_code)
39 return args, varargs, varkw, func_obj.func_defaults
40
41 #-----------------------------------------------------------------------------
42 # Testing functions
43
44 @dec.skip
45 def test_deliberately_broken():
46 """A deliberately broken test - we want to skip this one."""
47 1/0
48
49 @dec.skip('foo')
50 def test_deliberately_broken2():
51 """Another deliberately broken test - we want to skip this one."""
52 1/0
53
54
55 # Verify that we can correctly skip the doctest for a function at will, but
56 # that the docstring itself is NOT destroyed by the decorator.
57 @dec.skip_doctest
58 def doctest_bad(x,y=1,**k):
59 """A function whose doctest we need to skip.
60
61 >>> 1+1
62 3
63 """
64 print 'x:',x
65 print 'y:',y
66 print 'k:',k
67
68
69 def call_doctest_bad():
70 """Check that we can still call the decorated functions.
71
72 >>> doctest_bad(3,y=4)
73 x: 3
74 y: 4
75 k: {}
76 """
77 pass
78
79
80 def test_skip_dt_decorator():
81 """Doctest-skipping decorator should preserve the docstring.
82 """
83 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
84 check = """A function whose doctest we need to skip.
85
86 >>> 1+1
87 3
88 """
89 # Fetch the docstring from doctest_bad after decoration.
90 val = doctest_bad.__doc__
91
92 assert check==val,"doctest_bad docstrings don't match"
93
94 # Doctest skipping should work for class methods too
95 class foo(object):
96 """Foo
97
98 Example:
99
100 >>> 1+1
101 2
102 """
103
104 @dec.skip_doctest
105 def __init__(self,x):
106 """Make a foo.
107
108 Example:
109
110 >>> f = foo(3)
111 junk
112 """
113 print 'Making a foo.'
114 self.x = x
115
116 @dec.skip_doctest
117 def bar(self,y):
118 """Example:
119
120 >>> f = foo(3)
121 >>> f.bar(0)
122 boom!
123 >>> 1/0
124 bam!
125 """
126 return 1/y
127
128 def baz(self,y):
129 """Example:
130
131 >>> f = foo(3)
132 Making a foo.
133 >>> f.baz(3)
134 True
135 """
136 return self.x==y
137
138
139
140 def test_skip_dt_decorator2():
141 """Doctest-skipping decorator should preserve function signature.
142 """
143 # Hardcoded correct answer
144 dtargs = (['x', 'y'], None, 'k', (1,))
145 # Introspect out the value
146 dtargsr = getargspec(doctest_bad)
147 assert dtargsr==dtargs, \
148 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
149
150
151 @dec.skip_linux
152 def test_linux():
153 nt.assert_not_equals(sys.platform,'linux2',"This test can't run under linux")
154
155 @dec.skip_win32
156 def test_win32():
157 nt.assert_not_equals(sys.platform,'win32',"This test can't run under windows")
158
159 @dec.skip_osx
160 def test_osx():
161 nt.assert_not_equals(sys.platform,'darwin',"This test can't run under osx")
@@ -0,0 +1,20 b''
1 #!/usr/bin/env python
2 """Call the compile script to check that all code we ship compiles correctly.
3 """
4
5 import os
6 import sys
7
8
9 vstr = '.'.join(map(str,sys.version_info[:2]))
10
11 stat = os.system('python %s/lib/python%s/compileall.py .' % (sys.prefix,vstr))
12
13 print
14 if stat:
15 print '*** THERE WAS AN ERROR! ***'
16 print 'See messages above for the actual file that produced it.'
17 else:
18 print 'OK'
19
20 sys.exit(stat)
@@ -1,97 +1,121 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 #*****************************************************************************
4 #*****************************************************************************
5 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
5 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
6 #
6 #
7 # Copyright (c) 2001 Janko Hauser <jhauser@zscout.de> and Nathaniel Gray
7 # Copyright (c) 2001 Janko Hauser <jhauser@zscout.de> and Nathaniel Gray
8 # <n8gray@caltech.edu>
8 # <n8gray@caltech.edu>
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 # Name of the package for release purposes. This is the name which labels
14 # Name of the package for release purposes. This is the name which labels
15 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
15 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
16 name = 'ipython'
16 name = 'ipython'
17
17
18 # For versions with substrings (like 0.6.16.svn), use an extra . to separate
18 # For versions with substrings (like 0.6.16.svn), use an extra . to separate
19 # the new substring. We have to avoid using either dashes or underscores,
19 # the new substring. We have to avoid using either dashes or underscores,
20 # because bdist_rpm does not accept dashes (an RPM) convention, and
20 # because bdist_rpm does not accept dashes (an RPM) convention, and
21 # bdist_deb does not accept underscores (a Debian convention).
21 # bdist_deb does not accept underscores (a Debian convention).
22
22
23 development = False # change this to False to do a release
23 development = False # change this to False to do a release
24 version_base = '0.9'
24 version_base = '0.9.1'
25 branch = 'ipython'
25 branch = 'ipython'
26 revision = '1143'
26 revision = '1143'
27
27
28 if development:
28 if development:
29 if branch == 'ipython':
29 if branch == 'ipython':
30 version = '%s.bzr.r%s' % (version_base, revision)
30 version = '%s.bzr.r%s' % (version_base, revision)
31 else:
31 else:
32 version = '%s.bzr.r%s.%s' % (version_base, revision, branch)
32 version = '%s.bzr.r%s.%s' % (version_base, revision, branch)
33 else:
33 else:
34 version = version_base
34 version = version_base
35
35
36
36
37 description = "Tools for interactive development in Python."
37 description = "An interactive computing environment for Python"
38
38
39 long_description = \
39 long_description = \
40 """
40 """
41 IPython provides a replacement for the interactive Python interpreter with
41 The goal of IPython is to create a comprehensive environment for
42 extra functionality.
42 interactive and exploratory computing. To support this goal, IPython
43 has two main components:
43
44
44 Main features:
45 * An enhanced interactive Python shell.
46
47 * An architecture for interactive parallel computing.
48
49 The enhanced interactive Python shell has the following main features:
45
50
46 * Comprehensive object introspection.
51 * Comprehensive object introspection.
47
52
48 * Input history, persistent across sessions.
53 * Input history, persistent across sessions.
49
54
50 * Caching of output results during a session with automatically generated
55 * Caching of output results during a session with automatically generated
51 references.
56 references.
52
57
53 * Readline based name completion.
58 * Readline based name completion.
54
59
55 * Extensible system of 'magic' commands for controlling the environment and
60 * Extensible system of 'magic' commands for controlling the environment and
56 performing many tasks related either to IPython or the operating system.
61 performing many tasks related either to IPython or the operating system.
57
62
58 * Configuration system with easy switching between different setups (simpler
63 * Configuration system with easy switching between different setups (simpler
59 than changing $PYTHONSTARTUP environment variables every time).
64 than changing $PYTHONSTARTUP environment variables every time).
60
65
61 * Session logging and reloading.
66 * Session logging and reloading.
62
67
63 * Extensible syntax processing for special purpose situations.
68 * Extensible syntax processing for special purpose situations.
64
69
65 * Access to the system shell with user-extensible alias system.
70 * Access to the system shell with user-extensible alias system.
66
71
67 * Easily embeddable in other Python programs.
72 * Easily embeddable in other Python programs and wxPython GUIs.
68
73
69 * Integrated access to the pdb debugger and the Python profiler.
74 * Integrated access to the pdb debugger and the Python profiler.
70
75
71 The latest development version is always available at the IPython subversion
76 The parallel computing architecture has the following main features:
72 repository_.
77
78 * Quickly parallelize Python code from an interactive Python/IPython session.
79
80 * A flexible and dynamic process model that be deployed on anything from
81 multicore workstations to supercomputers.
82
83 * An architecture that supports many different styles of parallelism, from
84 message passing to task farming.
85
86 * Both blocking and fully asynchronous interfaces.
87
88 * High level APIs that enable many things to be parallelized in a few lines
89 of code.
90
91 * Share live parallel jobs with other users securely.
92
93 * Dynamically load balanced task farming system.
94
95 * Robust error handling in parallel code.
73
96
74 .. _repository: http://ipython.scipy.org/svn/ipython/ipython/trunk#egg=ipython-dev
97 The latest development version is always available from IPython's `Launchpad
98 site <http://launchpad.net/ipython>`_.
75 """
99 """
76
100
77 license = 'BSD'
101 license = 'BSD'
78
102
79 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
103 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
80 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
104 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
81 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
105 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
82 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
106 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
83 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
107 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
84 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
108 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
85 }
109 }
86
110
87 author = 'The IPython Development Team'
111 author = 'The IPython Development Team'
88
112
89 author_email = 'ipython-dev@scipy.org'
113 author_email = 'ipython-dev@scipy.org'
90
114
91 url = 'http://ipython.scipy.org'
115 url = 'http://ipython.scipy.org'
92
116
93 download_url = 'http://ipython.scipy.org/dist'
117 download_url = 'http://ipython.scipy.org/dist'
94
118
95 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
119 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
96
120
97 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
121 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
@@ -1,106 +1,102 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This is the official entry point to IPython's configuration system. """
3 """This is the official entry point to IPython's configuration system. """
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import os
18 import os
19 from os.path import join as pjoin
19 from os.path import join as pjoin
20
20
21 from IPython.genutils import get_home_dir, get_ipython_dir
21 from IPython.genutils import get_home_dir, get_ipython_dir
22 from IPython.external.configobj import ConfigObj
22 from IPython.external.configobj import ConfigObj
23
23
24 # Traitlets config imports
25 from IPython.config import traitlets
26 from IPython.config.config import *
27 from traitlets import *
28
24
29 class ConfigObjManager(object):
25 class ConfigObjManager(object):
30
26
31 def __init__(self, configObj, filename):
27 def __init__(self, configObj, filename):
32 self.current = configObj
28 self.current = configObj
33 self.current.indent_type = ' '
29 self.current.indent_type = ' '
34 self.filename = filename
30 self.filename = filename
35 # self.write_default_config_file()
31 # self.write_default_config_file()
36
32
37 def get_config_obj(self):
33 def get_config_obj(self):
38 return self.current
34 return self.current
39
35
40 def update_config_obj(self, newConfig):
36 def update_config_obj(self, newConfig):
41 self.current.merge(newConfig)
37 self.current.merge(newConfig)
42
38
43 def update_config_obj_from_file(self, filename):
39 def update_config_obj_from_file(self, filename):
44 newConfig = ConfigObj(filename, file_error=False)
40 newConfig = ConfigObj(filename, file_error=False)
45 self.current.merge(newConfig)
41 self.current.merge(newConfig)
46
42
47 def update_config_obj_from_default_file(self, ipythondir=None):
43 def update_config_obj_from_default_file(self, ipythondir=None):
48 fname = self.resolve_file_path(self.filename, ipythondir)
44 fname = self.resolve_file_path(self.filename, ipythondir)
49 self.update_config_obj_from_file(fname)
45 self.update_config_obj_from_file(fname)
50
46
51 def write_config_obj_to_file(self, filename):
47 def write_config_obj_to_file(self, filename):
52 f = open(filename, 'w')
48 f = open(filename, 'w')
53 self.current.write(f)
49 self.current.write(f)
54 f.close()
50 f.close()
55
51
56 def write_default_config_file(self):
52 def write_default_config_file(self):
57 ipdir = get_ipython_dir()
53 ipdir = get_ipython_dir()
58 fname = pjoin(ipdir, self.filename)
54 fname = pjoin(ipdir, self.filename)
59 if not os.path.isfile(fname):
55 if not os.path.isfile(fname):
60 print "Writing the configuration file to: " + fname
56 print "Writing the configuration file to: " + fname
61 self.write_config_obj_to_file(fname)
57 self.write_config_obj_to_file(fname)
62
58
63 def _import(self, key):
59 def _import(self, key):
64 package = '.'.join(key.split('.')[0:-1])
60 package = '.'.join(key.split('.')[0:-1])
65 obj = key.split('.')[-1]
61 obj = key.split('.')[-1]
66 execString = 'from %s import %s' % (package, obj)
62 execString = 'from %s import %s' % (package, obj)
67 exec execString
63 exec execString
68 exec 'temp = %s' % obj
64 exec 'temp = %s' % obj
69 return temp
65 return temp
70
66
71 def resolve_file_path(self, filename, ipythondir = None):
67 def resolve_file_path(self, filename, ipythondir = None):
72 """Resolve filenames into absolute paths.
68 """Resolve filenames into absolute paths.
73
69
74 This function looks in the following directories in order:
70 This function looks in the following directories in order:
75
71
76 1. In the current working directory or by absolute path with ~ expanded
72 1. In the current working directory or by absolute path with ~ expanded
77 2. In ipythondir if that is set
73 2. In ipythondir if that is set
78 3. In the IPYTHONDIR environment variable if it exists
74 3. In the IPYTHONDIR environment variable if it exists
79 4. In the ~/.ipython directory
75 4. In the ~/.ipython directory
80
76
81 Note: The IPYTHONDIR is also used by the trunk version of IPython so
77 Note: The IPYTHONDIR is also used by the trunk version of IPython so
82 changing it will also affect it was well.
78 changing it will also affect it was well.
83 """
79 """
84
80
85 # In cwd or by absolute path with ~ expanded
81 # In cwd or by absolute path with ~ expanded
86 trythis = os.path.expanduser(filename)
82 trythis = os.path.expanduser(filename)
87 if os.path.isfile(trythis):
83 if os.path.isfile(trythis):
88 return trythis
84 return trythis
89
85
90 # In ipythondir if it is set
86 # In ipythondir if it is set
91 if ipythondir is not None:
87 if ipythondir is not None:
92 trythis = pjoin(ipythondir, filename)
88 trythis = pjoin(ipythondir, filename)
93 if os.path.isfile(trythis):
89 if os.path.isfile(trythis):
94 return trythis
90 return trythis
95
91
96 trythis = pjoin(get_ipython_dir(), filename)
92 trythis = pjoin(get_ipython_dir(), filename)
97 if os.path.isfile(trythis):
93 if os.path.isfile(trythis):
98 return trythis
94 return trythis
99
95
100 return None
96 return None
101
97
102
98
103
99
104
100
105
101
106
102
@@ -1,76 +1,76 b''
1 """
1 """
2 Base front end class for all async frontends.
2 Base front end class for all async frontends.
3 """
3 """
4 __docformat__ = "restructuredtext en"
4 __docformat__ = "restructuredtext en"
5
5
6 #-------------------------------------------------------------------------------
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
7 # Copyright (C) 2008 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12
12
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 import uuid
17 from IPython.external import guid
18
18
19
19
20 from zope.interface import Interface, Attribute, implements, classProvides
20 from zope.interface import Interface, Attribute, implements, classProvides
21 from twisted.python.failure import Failure
21 from twisted.python.failure import Failure
22 from IPython.frontend.frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
22 from IPython.frontend.frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
23 from IPython.kernel.core.history import FrontEndHistory
23 from IPython.kernel.core.history import FrontEndHistory
24 from IPython.kernel.engineservice import IEngineCore
24 from IPython.kernel.engineservice import IEngineCore
25
25
26
26
27 class AsyncFrontEndBase(FrontEndBase):
27 class AsyncFrontEndBase(FrontEndBase):
28 """
28 """
29 Overrides FrontEndBase to wrap execute in a deferred result.
29 Overrides FrontEndBase to wrap execute in a deferred result.
30 All callbacks are made as callbacks on the deferred result.
30 All callbacks are made as callbacks on the deferred result.
31 """
31 """
32
32
33 implements(IFrontEnd)
33 implements(IFrontEnd)
34 classProvides(IFrontEndFactory)
34 classProvides(IFrontEndFactory)
35
35
36 def __init__(self, engine=None, history=None):
36 def __init__(self, engine=None, history=None):
37 assert(engine==None or IEngineCore.providedBy(engine))
37 assert(engine==None or IEngineCore.providedBy(engine))
38 self.engine = IEngineCore(engine)
38 self.engine = IEngineCore(engine)
39 if history is None:
39 if history is None:
40 self.history = FrontEndHistory(input_cache=[''])
40 self.history = FrontEndHistory(input_cache=[''])
41 else:
41 else:
42 self.history = history
42 self.history = history
43
43
44
44
45 def execute(self, block, blockID=None):
45 def execute(self, block, blockID=None):
46 """Execute the block and return the deferred result.
46 """Execute the block and return the deferred result.
47
47
48 Parameters:
48 Parameters:
49 block : {str, AST}
49 block : {str, AST}
50 blockID : any
50 blockID : any
51 Caller may provide an ID to identify this block.
51 Caller may provide an ID to identify this block.
52 result['blockID'] := blockID
52 result['blockID'] := blockID
53
53
54 Result:
54 Result:
55 Deferred result of self.interpreter.execute
55 Deferred result of self.interpreter.execute
56 """
56 """
57
57
58 if(not self.is_complete(block)):
58 if(not self.is_complete(block)):
59 return Failure(Exception("Block is not compilable"))
59 return Failure(Exception("Block is not compilable"))
60
60
61 if(blockID == None):
61 if(blockID == None):
62 blockID = uuid.uuid4() #random UUID
62 blockID = guid.generate()
63
63
64 d = self.engine.execute(block)
64 d = self.engine.execute(block)
65 d.addCallback(self._add_history, block=block)
65 d.addCallback(self._add_history, block=block)
66 d.addCallbacks(self._add_block_id_for_result,
66 d.addCallbacks(self._add_block_id_for_result,
67 errback=self._add_block_id_for_failure,
67 errback=self._add_block_id_for_failure,
68 callbackArgs=(blockID,),
68 callbackArgs=(blockID,),
69 errbackArgs=(blockID,))
69 errbackArgs=(blockID,))
70 d.addBoth(self.update_cell_prompt, blockID=blockID)
70 d.addBoth(self.update_cell_prompt, blockID=blockID)
71 d.addCallbacks(self.render_result,
71 d.addCallbacks(self.render_result,
72 errback=self.render_error)
72 errback=self.render_error)
73
73
74 return d
74 return d
75
75
76
76
@@ -1,560 +1,560 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 sys
28 import objc
28 import objc
29 import uuid
29 from IPython.external import guid
30
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
34 NSString, NSAutoreleasePool
35
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
38
38
39 from pprint import saferepr
39 from pprint import saferepr
40
40
41 import IPython
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44
44
45 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Classes to implement the Cocoa frontend
49 # Classes to implement the Cocoa frontend
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 # TODO:
52 # TODO:
53 # 1. use MultiEngineClient and out-of-process engine rather than
53 # 1. use MultiEngineClient and out-of-process engine rather than
54 # ThreadedEngineService?
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
56
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
58 """Wrap all blocks in an NSAutoreleasePool"""
59
59
60 def wrapped_execute(self, msg, lines):
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
61 """wrapped_execute"""
62 try:
62 try:
63 p = NSAutoreleasePool.alloc().init()
63 p = NSAutoreleasePool.alloc().init()
64 result = super(AutoreleasePoolWrappedThreadedEngineService,
64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 self).wrapped_execute(msg, lines)
65 self).wrapped_execute(msg, lines)
66 finally:
66 finally:
67 p.drain()
67 p.drain()
68
68
69 return result
69 return result
70
70
71
71
72
72
73 class Cell(NSObject):
73 class Cell(NSObject):
74 """
74 """
75 Representation of the prompts, input and output of a cell in the
75 Representation of the prompts, input and output of a cell in the
76 frontend
76 frontend
77 """
77 """
78
78
79 blockNumber = objc.ivar().unsigned_long()
79 blockNumber = objc.ivar().unsigned_long()
80 blockID = objc.ivar()
80 blockID = objc.ivar()
81 inputBlock = objc.ivar()
81 inputBlock = objc.ivar()
82 output = objc.ivar()
82 output = objc.ivar()
83
83
84
84
85
85
86 class CellBlock(object):
86 class CellBlock(object):
87 """
87 """
88 Storage for information about text ranges relating to a single cell
88 Storage for information about text ranges relating to a single cell
89 """
89 """
90
90
91
91
92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
93 outputRange=None):
93 outputRange=None):
94 super(CellBlock, self).__init__()
94 super(CellBlock, self).__init__()
95 self.inputPromptRange = inputPromptRange
95 self.inputPromptRange = inputPromptRange
96 self.inputRange = inputRange
96 self.inputRange = inputRange
97 self.outputPromptRange = outputPromptRange
97 self.outputPromptRange = outputPromptRange
98 self.outputRange = outputRange
98 self.outputRange = outputRange
99
99
100 def update_ranges_for_insertion(self, text, textRange):
100 def update_ranges_for_insertion(self, text, textRange):
101 """Update ranges for text insertion at textRange"""
101 """Update ranges for text insertion at textRange"""
102
102
103 for r in [self.inputPromptRange,self.inputRange,
103 for r in [self.inputPromptRange,self.inputRange,
104 self.outputPromptRange, self.outputRange]:
104 self.outputPromptRange, self.outputRange]:
105 if(r == None):
105 if(r == None):
106 continue
106 continue
107 intersection = NSIntersectionRange(r,textRange)
107 intersection = NSIntersectionRange(r,textRange)
108 if(intersection.length == 0): #ranges don't intersect
108 if(intersection.length == 0): #ranges don't intersect
109 if r.location >= textRange.location:
109 if r.location >= textRange.location:
110 r.location += len(text)
110 r.location += len(text)
111 else: #ranges intersect
111 else: #ranges intersect
112 if(r.location > textRange.location):
112 if(r.location > textRange.location):
113 offset = len(text) - intersection.length
113 offset = len(text) - intersection.length
114 r.length -= offset
114 r.length -= offset
115 r.location += offset
115 r.location += offset
116 elif(r.location == textRange.location):
116 elif(r.location == textRange.location):
117 r.length += len(text) - intersection.length
117 r.length += len(text) - intersection.length
118 else:
118 else:
119 r.length -= intersection.length
119 r.length -= intersection.length
120
120
121
121
122 def update_ranges_for_deletion(self, textRange):
122 def update_ranges_for_deletion(self, textRange):
123 """Update ranges for text deletion at textRange"""
123 """Update ranges for text deletion at textRange"""
124
124
125 for r in [self.inputPromptRange,self.inputRange,
125 for r in [self.inputPromptRange,self.inputRange,
126 self.outputPromptRange, self.outputRange]:
126 self.outputPromptRange, self.outputRange]:
127 if(r==None):
127 if(r==None):
128 continue
128 continue
129 intersection = NSIntersectionRange(r, textRange)
129 intersection = NSIntersectionRange(r, textRange)
130 if(intersection.length == 0): #ranges don't intersect
130 if(intersection.length == 0): #ranges don't intersect
131 if r.location >= textRange.location:
131 if r.location >= textRange.location:
132 r.location -= textRange.length
132 r.location -= textRange.length
133 else: #ranges intersect
133 else: #ranges intersect
134 if(r.location > textRange.location):
134 if(r.location > textRange.location):
135 offset = intersection.length
135 offset = intersection.length
136 r.length -= offset
136 r.length -= offset
137 r.location += offset
137 r.location += offset
138 elif(r.location == textRange.location):
138 elif(r.location == textRange.location):
139 r.length += intersection.length
139 r.length += intersection.length
140 else:
140 else:
141 r.length -= intersection.length
141 r.length -= intersection.length
142
142
143 def __repr__(self):
143 def __repr__(self):
144 return 'CellBlock('+ str((self.inputPromptRange,
144 return 'CellBlock('+ str((self.inputPromptRange,
145 self.inputRange,
145 self.inputRange,
146 self.outputPromptRange,
146 self.outputPromptRange,
147 self.outputRange)) + ')'
147 self.outputRange)) + ')'
148
148
149
149
150
150
151
151
152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
154 waitingForEngine = objc.ivar().bool()
154 waitingForEngine = objc.ivar().bool()
155 textView = objc.IBOutlet()
155 textView = objc.IBOutlet()
156
156
157 def init(self):
157 def init(self):
158 self = super(IPythonCocoaController, self).init()
158 self = super(IPythonCocoaController, self).init()
159 AsyncFrontEndBase.__init__(self,
159 AsyncFrontEndBase.__init__(self,
160 engine=AutoreleasePoolWrappedThreadedEngineService())
160 engine=AutoreleasePoolWrappedThreadedEngineService())
161 if(self != None):
161 if(self != None):
162 self._common_init()
162 self._common_init()
163
163
164 return self
164 return self
165
165
166 def _common_init(self):
166 def _common_init(self):
167 """_common_init"""
167 """_common_init"""
168
168
169 self.userNS = NSMutableDictionary.dictionary()
169 self.userNS = NSMutableDictionary.dictionary()
170 self.waitingForEngine = False
170 self.waitingForEngine = False
171
171
172 self.lines = {}
172 self.lines = {}
173 self.tabSpaces = 4
173 self.tabSpaces = 4
174 self.tabUsesSpaces = True
174 self.tabUsesSpaces = True
175 self.currentBlockID = self.next_block_ID()
175 self.currentBlockID = self.next_block_ID()
176 self.blockRanges = {} # blockID=>CellBlock
176 self.blockRanges = {} # blockID=>CellBlock
177
177
178
178
179 def awakeFromNib(self):
179 def awakeFromNib(self):
180 """awakeFromNib"""
180 """awakeFromNib"""
181
181
182 self._common_init()
182 self._common_init()
183
183
184 # Start the IPython engine
184 # Start the IPython engine
185 self.engine.startService()
185 self.engine.startService()
186 NSLog('IPython engine started')
186 NSLog('IPython engine started')
187
187
188 # Register for app termination
188 # Register for app termination
189 nc = NSNotificationCenter.defaultCenter()
189 nc = NSNotificationCenter.defaultCenter()
190 nc.addObserver_selector_name_object_(
190 nc.addObserver_selector_name_object_(
191 self,
191 self,
192 'appWillTerminate:',
192 'appWillTerminate:',
193 NSApplicationWillTerminateNotification,
193 NSApplicationWillTerminateNotification,
194 None)
194 None)
195
195
196 self.textView.setDelegate_(self)
196 self.textView.setDelegate_(self)
197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
198 r = NSRulerView.alloc().initWithScrollView_orientation_(
198 r = NSRulerView.alloc().initWithScrollView_orientation_(
199 self.textView.enclosingScrollView(),
199 self.textView.enclosingScrollView(),
200 NSVerticalRuler)
200 NSVerticalRuler)
201 self.verticalRulerView = r
201 self.verticalRulerView = r
202 self.verticalRulerView.setClientView_(self.textView)
202 self.verticalRulerView.setClientView_(self.textView)
203 self._start_cli_banner()
203 self._start_cli_banner()
204 self.start_new_block()
204 self.start_new_block()
205
205
206
206
207 def appWillTerminate_(self, notification):
207 def appWillTerminate_(self, notification):
208 """appWillTerminate"""
208 """appWillTerminate"""
209
209
210 self.engine.stopService()
210 self.engine.stopService()
211
211
212
212
213 def complete(self, token):
213 def complete(self, token):
214 """Complete token in engine's user_ns
214 """Complete token in engine's user_ns
215
215
216 Parameters
216 Parameters
217 ----------
217 ----------
218 token : string
218 token : string
219
219
220 Result
220 Result
221 ------
221 ------
222 Deferred result of
222 Deferred result of
223 IPython.kernel.engineservice.IEngineBase.complete
223 IPython.kernel.engineservice.IEngineBase.complete
224 """
224 """
225
225
226 return self.engine.complete(token)
226 return self.engine.complete(token)
227
227
228
228
229 def execute(self, block, blockID=None):
229 def execute(self, block, blockID=None):
230 self.waitingForEngine = True
230 self.waitingForEngine = True
231 self.willChangeValueForKey_('commandHistory')
231 self.willChangeValueForKey_('commandHistory')
232 d = super(IPythonCocoaController, self).execute(block,
232 d = super(IPythonCocoaController, self).execute(block,
233 blockID)
233 blockID)
234 d.addBoth(self._engine_done)
234 d.addBoth(self._engine_done)
235 d.addCallback(self._update_user_ns)
235 d.addCallback(self._update_user_ns)
236
236
237 return d
237 return d
238
238
239
239
240 def push_(self, namespace):
240 def push_(self, namespace):
241 """Push dictionary of key=>values to python namespace"""
241 """Push dictionary of key=>values to python namespace"""
242
242
243 self.waitingForEngine = True
243 self.waitingForEngine = True
244 self.willChangeValueForKey_('commandHistory')
244 self.willChangeValueForKey_('commandHistory')
245 d = self.engine.push(namespace)
245 d = self.engine.push(namespace)
246 d.addBoth(self._engine_done)
246 d.addBoth(self._engine_done)
247 d.addCallback(self._update_user_ns)
247 d.addCallback(self._update_user_ns)
248
248
249
249
250 def pull_(self, keys):
250 def pull_(self, keys):
251 """Pull keys from python namespace"""
251 """Pull keys from python namespace"""
252
252
253 self.waitingForEngine = True
253 self.waitingForEngine = True
254 result = blockingCallFromThread(self.engine.pull, keys)
254 result = blockingCallFromThread(self.engine.pull, keys)
255 self.waitingForEngine = False
255 self.waitingForEngine = False
256
256
257 @objc.signature('v@:@I')
257 @objc.signature('v@:@I')
258 def executeFileAtPath_encoding_(self, path, encoding):
258 def executeFileAtPath_encoding_(self, path, encoding):
259 """Execute file at path in an empty namespace. Update the engine
259 """Execute file at path in an empty namespace. Update the engine
260 user_ns with the resulting locals."""
260 user_ns with the resulting locals."""
261
261
262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
263 path,
263 path,
264 encoding,
264 encoding,
265 None)
265 None)
266 self.engine.execute(lines)
266 self.engine.execute(lines)
267
267
268
268
269 def _engine_done(self, x):
269 def _engine_done(self, x):
270 self.waitingForEngine = False
270 self.waitingForEngine = False
271 self.didChangeValueForKey_('commandHistory')
271 self.didChangeValueForKey_('commandHistory')
272 return x
272 return x
273
273
274 def _update_user_ns(self, result):
274 def _update_user_ns(self, result):
275 """Update self.userNS from self.engine's namespace"""
275 """Update self.userNS from self.engine's namespace"""
276 d = self.engine.keys()
276 d = self.engine.keys()
277 d.addCallback(self._get_engine_namespace_values_for_keys)
277 d.addCallback(self._get_engine_namespace_values_for_keys)
278
278
279 return result
279 return result
280
280
281
281
282 def _get_engine_namespace_values_for_keys(self, keys):
282 def _get_engine_namespace_values_for_keys(self, keys):
283 d = self.engine.pull(keys)
283 d = self.engine.pull(keys)
284 d.addCallback(self._store_engine_namespace_values, keys=keys)
284 d.addCallback(self._store_engine_namespace_values, keys=keys)
285
285
286
286
287 def _store_engine_namespace_values(self, values, keys=[]):
287 def _store_engine_namespace_values(self, values, keys=[]):
288 assert(len(values) == len(keys))
288 assert(len(values) == len(keys))
289 self.willChangeValueForKey_('userNS')
289 self.willChangeValueForKey_('userNS')
290 for (k,v) in zip(keys,values):
290 for (k,v) in zip(keys,values):
291 self.userNS[k] = saferepr(v)
291 self.userNS[k] = saferepr(v)
292 self.didChangeValueForKey_('userNS')
292 self.didChangeValueForKey_('userNS')
293
293
294
294
295 def update_cell_prompt(self, result, blockID=None):
295 def update_cell_prompt(self, result, blockID=None):
296 print self.blockRanges
296 print self.blockRanges
297 if(isinstance(result, Failure)):
297 if(isinstance(result, Failure)):
298 prompt = self.input_prompt()
298 prompt = self.input_prompt()
299
299
300 else:
300 else:
301 prompt = self.input_prompt(number=result['number'])
301 prompt = self.input_prompt(number=result['number'])
302
302
303 r = self.blockRanges[blockID].inputPromptRange
303 r = self.blockRanges[blockID].inputPromptRange
304 self.insert_text(prompt,
304 self.insert_text(prompt,
305 textRange=r,
305 textRange=r,
306 scrollToVisible=False
306 scrollToVisible=False
307 )
307 )
308
308
309 return result
309 return result
310
310
311
311
312 def render_result(self, result):
312 def render_result(self, result):
313 blockID = result['blockID']
313 blockID = result['blockID']
314 inputRange = self.blockRanges[blockID].inputRange
314 inputRange = self.blockRanges[blockID].inputRange
315 del self.blockRanges[blockID]
315 del self.blockRanges[blockID]
316
316
317 #print inputRange,self.current_block_range()
317 #print inputRange,self.current_block_range()
318 self.insert_text('\n' +
318 self.insert_text('\n' +
319 self.output_prompt(number=result['number']) +
319 self.output_prompt(number=result['number']) +
320 result.get('display',{}).get('pprint','') +
320 result.get('display',{}).get('pprint','') +
321 '\n\n',
321 '\n\n',
322 textRange=NSMakeRange(inputRange.location+inputRange.length,
322 textRange=NSMakeRange(inputRange.location+inputRange.length,
323 0))
323 0))
324 return result
324 return result
325
325
326
326
327 def render_error(self, failure):
327 def render_error(self, failure):
328 print failure
328 print failure
329 blockID = failure.blockID
329 blockID = failure.blockID
330 inputRange = self.blockRanges[blockID].inputRange
330 inputRange = self.blockRanges[blockID].inputRange
331 self.insert_text('\n' +
331 self.insert_text('\n' +
332 self.output_prompt() +
332 self.output_prompt() +
333 '\n' +
333 '\n' +
334 failure.getErrorMessage() +
334 failure.getErrorMessage() +
335 '\n\n',
335 '\n\n',
336 textRange=NSMakeRange(inputRange.location +
336 textRange=NSMakeRange(inputRange.location +
337 inputRange.length,
337 inputRange.length,
338 0))
338 0))
339 self.start_new_block()
339 self.start_new_block()
340 return failure
340 return failure
341
341
342
342
343 def _start_cli_banner(self):
343 def _start_cli_banner(self):
344 """Print banner"""
344 """Print banner"""
345
345
346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
347 IPython.__version__
347 IPython.__version__
348
348
349 self.insert_text(banner + '\n\n')
349 self.insert_text(banner + '\n\n')
350
350
351
351
352 def start_new_block(self):
352 def start_new_block(self):
353 """"""
353 """"""
354
354
355 self.currentBlockID = self.next_block_ID()
355 self.currentBlockID = self.next_block_ID()
356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
357 self.insert_text(self.input_prompt(),
357 self.insert_text(self.input_prompt(),
358 textRange=self.current_block_range().inputPromptRange)
358 textRange=self.current_block_range().inputPromptRange)
359
359
360
360
361
361
362 def next_block_ID(self):
362 def next_block_ID(self):
363
363
364 return uuid.uuid4()
364 return guid.generate()
365
365
366 def new_cell_block(self):
366 def new_cell_block(self):
367 """A new CellBlock at the end of self.textView.textStorage()"""
367 """A new CellBlock at the end of self.textView.textStorage()"""
368
368
369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
370 0), #len(self.input_prompt())),
370 0), #len(self.input_prompt())),
371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
372 0))
372 0))
373
373
374
374
375 def current_block_range(self):
375 def current_block_range(self):
376 return self.blockRanges.get(self.currentBlockID,
376 return self.blockRanges.get(self.currentBlockID,
377 self.new_cell_block())
377 self.new_cell_block())
378
378
379 def current_block(self):
379 def current_block(self):
380 """The current block's text"""
380 """The current block's text"""
381
381
382 return self.text_for_range(self.current_block_range().inputRange)
382 return self.text_for_range(self.current_block_range().inputRange)
383
383
384 def text_for_range(self, textRange):
384 def text_for_range(self, textRange):
385 """text_for_range"""
385 """text_for_range"""
386
386
387 ts = self.textView.textStorage()
387 ts = self.textView.textStorage()
388 return ts.string().substringWithRange_(textRange)
388 return ts.string().substringWithRange_(textRange)
389
389
390 def current_line(self):
390 def current_line(self):
391 block = self.text_for_range(self.current_block_range().inputRange)
391 block = self.text_for_range(self.current_block_range().inputRange)
392 block = block.split('\n')
392 block = block.split('\n')
393 return block[-1]
393 return block[-1]
394
394
395
395
396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
397 """Insert text into textView at textRange, updating blockRanges
397 """Insert text into textView at textRange, updating blockRanges
398 as necessary
398 as necessary
399 """
399 """
400 if(textRange == None):
400 if(textRange == None):
401 #range for end of text
401 #range for end of text
402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
403
403
404
404
405 self.textView.replaceCharactersInRange_withString_(
405 self.textView.replaceCharactersInRange_withString_(
406 textRange, string)
406 textRange, string)
407
407
408 for r in self.blockRanges.itervalues():
408 for r in self.blockRanges.itervalues():
409 r.update_ranges_for_insertion(string, textRange)
409 r.update_ranges_for_insertion(string, textRange)
410
410
411 self.textView.setSelectedRange_(textRange)
411 self.textView.setSelectedRange_(textRange)
412 if(scrollToVisible):
412 if(scrollToVisible):
413 self.textView.scrollRangeToVisible_(textRange)
413 self.textView.scrollRangeToVisible_(textRange)
414
414
415
415
416
416
417 def replace_current_block_with_string(self, textView, string):
417 def replace_current_block_with_string(self, textView, string):
418 textView.replaceCharactersInRange_withString_(
418 textView.replaceCharactersInRange_withString_(
419 self.current_block_range().inputRange,
419 self.current_block_range().inputRange,
420 string)
420 string)
421 self.current_block_range().inputRange.length = len(string)
421 self.current_block_range().inputRange.length = len(string)
422 r = NSMakeRange(textView.textStorage().length(), 0)
422 r = NSMakeRange(textView.textStorage().length(), 0)
423 textView.scrollRangeToVisible_(r)
423 textView.scrollRangeToVisible_(r)
424 textView.setSelectedRange_(r)
424 textView.setSelectedRange_(r)
425
425
426
426
427 def current_indent_string(self):
427 def current_indent_string(self):
428 """returns string for indent or None if no indent"""
428 """returns string for indent or None if no indent"""
429
429
430 return self._indent_for_block(self.current_block())
430 return self._indent_for_block(self.current_block())
431
431
432
432
433 def _indent_for_block(self, block):
433 def _indent_for_block(self, block):
434 lines = block.split('\n')
434 lines = block.split('\n')
435 if(len(lines) > 1):
435 if(len(lines) > 1):
436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
437 if(currentIndent == 0):
437 if(currentIndent == 0):
438 currentIndent = self.tabSpaces
438 currentIndent = self.tabSpaces
439
439
440 if(self.tabUsesSpaces):
440 if(self.tabUsesSpaces):
441 result = ' ' * currentIndent
441 result = ' ' * currentIndent
442 else:
442 else:
443 result = '\t' * (currentIndent/self.tabSpaces)
443 result = '\t' * (currentIndent/self.tabSpaces)
444 else:
444 else:
445 result = None
445 result = None
446
446
447 return result
447 return result
448
448
449
449
450 # NSTextView delegate methods...
450 # NSTextView delegate methods...
451 def textView_doCommandBySelector_(self, textView, selector):
451 def textView_doCommandBySelector_(self, textView, selector):
452 assert(textView == self.textView)
452 assert(textView == self.textView)
453 NSLog("textView_doCommandBySelector_: "+selector)
453 NSLog("textView_doCommandBySelector_: "+selector)
454
454
455
455
456 if(selector == 'insertNewline:'):
456 if(selector == 'insertNewline:'):
457 indent = self.current_indent_string()
457 indent = self.current_indent_string()
458 if(indent):
458 if(indent):
459 line = indent + self.current_line()
459 line = indent + self.current_line()
460 else:
460 else:
461 line = self.current_line()
461 line = self.current_line()
462
462
463 if(self.is_complete(self.current_block())):
463 if(self.is_complete(self.current_block())):
464 self.execute(self.current_block(),
464 self.execute(self.current_block(),
465 blockID=self.currentBlockID)
465 blockID=self.currentBlockID)
466 self.start_new_block()
466 self.start_new_block()
467
467
468 return True
468 return True
469
469
470 return False
470 return False
471
471
472 elif(selector == 'moveUp:'):
472 elif(selector == 'moveUp:'):
473 prevBlock = self.get_history_previous(self.current_block())
473 prevBlock = self.get_history_previous(self.current_block())
474 if(prevBlock != None):
474 if(prevBlock != None):
475 self.replace_current_block_with_string(textView, prevBlock)
475 self.replace_current_block_with_string(textView, prevBlock)
476 else:
476 else:
477 NSBeep()
477 NSBeep()
478 return True
478 return True
479
479
480 elif(selector == 'moveDown:'):
480 elif(selector == 'moveDown:'):
481 nextBlock = self.get_history_next()
481 nextBlock = self.get_history_next()
482 if(nextBlock != None):
482 if(nextBlock != None):
483 self.replace_current_block_with_string(textView, nextBlock)
483 self.replace_current_block_with_string(textView, nextBlock)
484 else:
484 else:
485 NSBeep()
485 NSBeep()
486 return True
486 return True
487
487
488 elif(selector == 'moveToBeginningOfParagraph:'):
488 elif(selector == 'moveToBeginningOfParagraph:'):
489 textView.setSelectedRange_(NSMakeRange(
489 textView.setSelectedRange_(NSMakeRange(
490 self.current_block_range().inputRange.location,
490 self.current_block_range().inputRange.location,
491 0))
491 0))
492 return True
492 return True
493 elif(selector == 'moveToEndOfParagraph:'):
493 elif(selector == 'moveToEndOfParagraph:'):
494 textView.setSelectedRange_(NSMakeRange(
494 textView.setSelectedRange_(NSMakeRange(
495 self.current_block_range().inputRange.location + \
495 self.current_block_range().inputRange.location + \
496 self.current_block_range().inputRange.length, 0))
496 self.current_block_range().inputRange.length, 0))
497 return True
497 return True
498 elif(selector == 'deleteToEndOfParagraph:'):
498 elif(selector == 'deleteToEndOfParagraph:'):
499 if(textView.selectedRange().location <= \
499 if(textView.selectedRange().location <= \
500 self.current_block_range().location):
500 self.current_block_range().location):
501 raise NotImplemented()
501 raise NotImplemented()
502
502
503 return False # don't actually handle the delete
503 return False # don't actually handle the delete
504
504
505 elif(selector == 'insertTab:'):
505 elif(selector == 'insertTab:'):
506 if(len(self.current_line().strip()) == 0): #only white space
506 if(len(self.current_line().strip()) == 0): #only white space
507 return False
507 return False
508 else:
508 else:
509 self.textView.complete_(self)
509 self.textView.complete_(self)
510 return True
510 return True
511
511
512 elif(selector == 'deleteBackward:'):
512 elif(selector == 'deleteBackward:'):
513 #if we're at the beginning of the current block, ignore
513 #if we're at the beginning of the current block, ignore
514 if(textView.selectedRange().location == \
514 if(textView.selectedRange().location == \
515 self.current_block_range().inputRange.location):
515 self.current_block_range().inputRange.location):
516 return True
516 return True
517 else:
517 else:
518 for r in self.blockRanges.itervalues():
518 for r in self.blockRanges.itervalues():
519 deleteRange = textView.selectedRange
519 deleteRange = textView.selectedRange
520 if(deleteRange.length == 0):
520 if(deleteRange.length == 0):
521 deleteRange.location -= 1
521 deleteRange.location -= 1
522 deleteRange.length = 1
522 deleteRange.length = 1
523 r.update_ranges_for_deletion(deleteRange)
523 r.update_ranges_for_deletion(deleteRange)
524 return False
524 return False
525 return False
525 return False
526
526
527
527
528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
529 textView, ranges, replacementStrings):
529 textView, ranges, replacementStrings):
530 """
530 """
531 Delegate method for NSTextView.
531 Delegate method for NSTextView.
532
532
533 Refuse change text in ranges not at end, but make those changes at
533 Refuse change text in ranges not at end, but make those changes at
534 end.
534 end.
535 """
535 """
536
536
537 assert(len(ranges) == len(replacementStrings))
537 assert(len(ranges) == len(replacementStrings))
538 allow = True
538 allow = True
539 for r,s in zip(ranges, replacementStrings):
539 for r,s in zip(ranges, replacementStrings):
540 r = r.rangeValue()
540 r = r.rangeValue()
541 if(textView.textStorage().length() > 0 and
541 if(textView.textStorage().length() > 0 and
542 r.location < self.current_block_range().inputRange.location):
542 r.location < self.current_block_range().inputRange.location):
543 self.insert_text(s)
543 self.insert_text(s)
544 allow = False
544 allow = False
545
545
546 return allow
546 return allow
547
547
548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
549 textView, words, charRange, index):
549 textView, words, charRange, index):
550 try:
550 try:
551 ts = textView.textStorage()
551 ts = textView.textStorage()
552 token = ts.string().substringWithRange_(charRange)
552 token = ts.string().substringWithRange_(charRange)
553 completions = blockingCallFromThread(self.complete, token)
553 completions = blockingCallFromThread(self.complete, token)
554 except:
554 except:
555 completions = objc.nil
555 completions = objc.nil
556 NSBeep()
556 NSBeep()
557
557
558 return (completions,0)
558 return (completions,0)
559
559
560
560
@@ -1,362 +1,343 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 codeop
25 import _ast
25 from IPython.external import guid
26
26
27
27 from IPython.frontend.zopeinterface import (
28 from IPython.frontend.zopeinterface import (
28 Interface,
29 Interface,
29 Attribute,
30 Attribute,
30 implements,
31 classProvides
32 )
31 )
33 from IPython.kernel.core.history import FrontEndHistory
32 from IPython.kernel.core.history import FrontEndHistory
34 from IPython.kernel.core.util import Bunch
33 from IPython.kernel.core.util import Bunch
35
34
36 ##############################################################################
35 ##############################################################################
37 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
38 # not
37 # not
39
38
40 rc = Bunch()
39 rc = Bunch()
41 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in1 = r'In [$number]: '
42 rc.prompt_in2 = r'...'
41 rc.prompt_in2 = r'...'
43 rc.prompt_out = r'Out [$number]: '
42 rc.prompt_out = r'Out [$number]: '
44
43
45 ##############################################################################
44 ##############################################################################
46 # Interface definitions
45 # Interface definitions
47 ##############################################################################
46 ##############################################################################
48
47
49 class IFrontEndFactory(Interface):
48 class IFrontEndFactory(Interface):
50 """Factory interface for frontends."""
49 """Factory interface for frontends."""
51
50
52 def __call__(engine=None, history=None):
51 def __call__(engine=None, history=None):
53 """
52 """
54 Parameters:
53 Parameters:
55 interpreter : IPython.kernel.engineservice.IEngineCore
54 interpreter : IPython.kernel.engineservice.IEngineCore
56 """
55 """
57
56
58 pass
57 pass
59
58
60
59
61 class IFrontEnd(Interface):
60 class IFrontEnd(Interface):
62 """Interface for frontends. All methods return t.i.d.Deferred"""
61 """Interface for frontends. All methods return t.i.d.Deferred"""
63
62
64 Attribute("input_prompt_template", "string.Template instance\
63 Attribute("input_prompt_template", "string.Template instance\
65 substituteable with execute result.")
64 substituteable with execute result.")
66 Attribute("output_prompt_template", "string.Template instance\
65 Attribute("output_prompt_template", "string.Template instance\
67 substituteable with execute result.")
66 substituteable with execute result.")
68 Attribute("continuation_prompt_template", "string.Template instance\
67 Attribute("continuation_prompt_template", "string.Template instance\
69 substituteable with execute result.")
68 substituteable with execute result.")
70
69
71 def update_cell_prompt(result, blockID=None):
70 def update_cell_prompt(result, blockID=None):
72 """Subclass may override to update the input prompt for a block.
71 """Subclass may override to update the input prompt for a block.
73
72
74 In asynchronous frontends, this method will be called as a
73 In asynchronous frontends, this method will be called as a
75 twisted.internet.defer.Deferred's callback/errback.
74 twisted.internet.defer.Deferred's callback/errback.
76 Implementations should thus return result when finished.
75 Implementations should thus return result when finished.
77
76
78 Result is a result dict in case of success, and a
77 Result is a result dict in case of success, and a
79 twisted.python.util.failure.Failure in case of an error
78 twisted.python.util.failure.Failure in case of an error
80 """
79 """
81
80
82 pass
81 pass
83
82
84 def render_result(result):
83 def render_result(result):
85 """Render the result of an execute call. Implementors may choose the
84 """Render the result of an execute call. Implementors may choose the
86 method of rendering.
85 method of rendering.
87 For example, a notebook-style frontend might render a Chaco plot
86 For example, a notebook-style frontend might render a Chaco plot
88 inline.
87 inline.
89
88
90 Parameters:
89 Parameters:
91 result : dict (result of IEngineBase.execute )
90 result : dict (result of IEngineBase.execute )
92 blockID = result['blockID']
91 blockID = result['blockID']
93
92
94 Result:
93 Result:
95 Output of frontend rendering
94 Output of frontend rendering
96 """
95 """
97
96
98 pass
97 pass
99
98
100 def render_error(failure):
99 def render_error(failure):
101 """Subclasses must override to render the failure.
100 """Subclasses must override to render the failure.
102
101
103 In asynchronous frontend, since this method will be called as a
102 In asynchronous frontend, since this method will be called as a
104 twisted.internet.defer.Deferred's callback. Implementations
103 twisted.internet.defer.Deferred's callback. Implementations
105 should thus return result when finished.
104 should thus return result when finished.
106
105
107 blockID = failure.blockID
106 blockID = failure.blockID
108 """
107 """
109
108
110 pass
109 pass
111
110
112 def input_prompt(number=''):
111 def input_prompt(number=''):
113 """Returns the input prompt by subsituting into
112 """Returns the input prompt by subsituting into
114 self.input_prompt_template
113 self.input_prompt_template
115 """
114 """
116 pass
115 pass
117
116
118 def output_prompt(number=''):
117 def output_prompt(number=''):
119 """Returns the output prompt by subsituting into
118 """Returns the output prompt by subsituting into
120 self.output_prompt_template
119 self.output_prompt_template
121 """
120 """
122
121
123 pass
122 pass
124
123
125 def continuation_prompt():
124 def continuation_prompt():
126 """Returns the continuation prompt by subsituting into
125 """Returns the continuation prompt by subsituting into
127 self.continuation_prompt_template
126 self.continuation_prompt_template
128 """
127 """
129
128
130 pass
129 pass
131
130
132 def is_complete(block):
131 def is_complete(block):
133 """Returns True if block is complete, False otherwise."""
132 """Returns True if block is complete, False otherwise."""
134
133
135 pass
134 pass
136
135
137 def compile_ast(block):
138 """Compiles block to an _ast.AST"""
139
140 pass
141
136
142 def get_history_previous(current_block):
137 def get_history_previous(current_block):
143 """Returns the block previous in the history. Saves currentBlock if
138 """Returns the block previous in the history. Saves currentBlock if
144 the history_cursor is currently at the end of the input history"""
139 the history_cursor is currently at the end of the input history"""
145 pass
140 pass
146
141
147 def get_history_next():
142 def get_history_next():
148 """Returns the next block in the history."""
143 """Returns the next block in the history."""
149
144
150 pass
145 pass
151
146
152 def complete(self, line):
147 def complete(self, line):
153 """Returns the list of possible completions, and the completed
148 """Returns the list of possible completions, and the completed
154 line.
149 line.
155
150
156 The input argument is the full line to be completed. This method
151 The input argument is the full line to be completed. This method
157 returns both the line completed as much as possible, and the list
152 returns both the line completed as much as possible, and the list
158 of further possible completions (full words).
153 of further possible completions (full words).
159 """
154 """
160 pass
155 pass
161
156
162
157
163 ##############################################################################
158 ##############################################################################
164 # Base class for all the frontends.
159 # Base class for all the frontends.
165 ##############################################################################
160 ##############################################################################
166
161
167 class FrontEndBase(object):
162 class FrontEndBase(object):
168 """
163 """
169 FrontEndBase manages the state tasks for a CLI frontend:
164 FrontEndBase manages the state tasks for a CLI frontend:
170 - Input and output history management
165 - Input and output history management
171 - Input/continuation and output prompt generation
166 - Input/continuation and output prompt generation
172
167
173 Some issues (due to possibly unavailable engine):
168 Some issues (due to possibly unavailable engine):
174 - How do we get the current cell number for the engine?
169 - How do we get the current cell number for the engine?
175 - How do we handle completions?
170 - How do we handle completions?
176 """
171 """
177
172
178 history_cursor = 0
173 history_cursor = 0
179
174
180 input_prompt_template = string.Template(rc.prompt_in1)
175 input_prompt_template = string.Template(rc.prompt_in1)
181 output_prompt_template = string.Template(rc.prompt_out)
176 output_prompt_template = string.Template(rc.prompt_out)
182 continuation_prompt_template = string.Template(rc.prompt_in2)
177 continuation_prompt_template = string.Template(rc.prompt_in2)
183
178
184 def __init__(self, shell=None, history=None):
179 def __init__(self, shell=None, history=None):
185 self.shell = shell
180 self.shell = shell
186 if history is None:
181 if history is None:
187 self.history = FrontEndHistory(input_cache=[''])
182 self.history = FrontEndHistory(input_cache=[''])
188 else:
183 else:
189 self.history = history
184 self.history = history
190
185
191
186
192 def input_prompt(self, number=''):
187 def input_prompt(self, number=''):
193 """Returns the current input prompt
188 """Returns the current input prompt
194
189
195 It would be great to use ipython1.core.prompts.Prompt1 here
190 It would be great to use ipython1.core.prompts.Prompt1 here
196 """
191 """
197 return self.input_prompt_template.safe_substitute({'number':number})
192 return self.input_prompt_template.safe_substitute({'number':number})
198
193
199
194
200 def continuation_prompt(self):
195 def continuation_prompt(self):
201 """Returns the current continuation prompt"""
196 """Returns the current continuation prompt"""
202
197
203 return self.continuation_prompt_template.safe_substitute()
198 return self.continuation_prompt_template.safe_substitute()
204
199
205 def output_prompt(self, number=''):
200 def output_prompt(self, number=''):
206 """Returns the output prompt for result"""
201 """Returns the output prompt for result"""
207
202
208 return self.output_prompt_template.safe_substitute({'number':number})
203 return self.output_prompt_template.safe_substitute({'number':number})
209
204
210
205
211 def is_complete(self, block):
206 def is_complete(self, block):
212 """Determine if block is complete.
207 """Determine if block is complete.
213
208
214 Parameters
209 Parameters
215 block : string
210 block : string
216
211
217 Result
212 Result
218 True if block can be sent to the engine without compile errors.
213 True if block can be sent to the engine without compile errors.
219 False otherwise.
214 False otherwise.
220 """
215 """
221
216
222 try:
217 try:
223 ast = self.compile_ast(block)
218 is_complete = codeop.compile_command(block.rstrip() + '\n\n',
219 "<string>", "exec")
224 except:
220 except:
225 return False
221 return False
226
222
227 lines = block.split('\n')
223 lines = block.split('\n')
228 return (len(lines)==1 or str(lines[-1])=='')
224 return ((is_complete is not None)
229
225 and (len(lines)==1 or str(lines[-1])==''))
230
231 def compile_ast(self, block):
232 """Compile block to an AST
233
234 Parameters:
235 block : str
236
237 Result:
238 AST
239
240 Throws:
241 Exception if block cannot be compiled
242 """
243
244 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
245
226
246
227
247 def execute(self, block, blockID=None):
228 def execute(self, block, blockID=None):
248 """Execute the block and return the result.
229 """Execute the block and return the result.
249
230
250 Parameters:
231 Parameters:
251 block : {str, AST}
232 block : {str, AST}
252 blockID : any
233 blockID : any
253 Caller may provide an ID to identify this block.
234 Caller may provide an ID to identify this block.
254 result['blockID'] := blockID
235 result['blockID'] := blockID
255
236
256 Result:
237 Result:
257 Deferred result of self.interpreter.execute
238 Deferred result of self.interpreter.execute
258 """
239 """
259
240
260 if(not self.is_complete(block)):
241 if(not self.is_complete(block)):
261 raise Exception("Block is not compilable")
242 raise Exception("Block is not compilable")
262
243
263 if(blockID == None):
244 if(blockID == None):
264 blockID = uuid.uuid4() #random UUID
245 blockID = guid.generate()
265
246
266 try:
247 try:
267 result = self.shell.execute(block)
248 result = self.shell.execute(block)
268 except Exception,e:
249 except Exception,e:
269 e = self._add_block_id_for_failure(e, blockID=blockID)
250 e = self._add_block_id_for_failure(e, blockID=blockID)
270 e = self.update_cell_prompt(e, blockID=blockID)
251 e = self.update_cell_prompt(e, blockID=blockID)
271 e = self.render_error(e)
252 e = self.render_error(e)
272 else:
253 else:
273 result = self._add_block_id_for_result(result, blockID=blockID)
254 result = self._add_block_id_for_result(result, blockID=blockID)
274 result = self.update_cell_prompt(result, blockID=blockID)
255 result = self.update_cell_prompt(result, blockID=blockID)
275 result = self.render_result(result)
256 result = self.render_result(result)
276
257
277 return result
258 return result
278
259
279
260
280 def _add_block_id_for_result(self, result, blockID):
261 def _add_block_id_for_result(self, result, blockID):
281 """Add the blockID to result or failure. Unfortunatley, we have to
262 """Add the blockID to result or failure. Unfortunatley, we have to
282 treat failures differently than result dicts.
263 treat failures differently than result dicts.
283 """
264 """
284
265
285 result['blockID'] = blockID
266 result['blockID'] = blockID
286
267
287 return result
268 return result
288
269
289 def _add_block_id_for_failure(self, failure, blockID):
270 def _add_block_id_for_failure(self, failure, blockID):
290 """_add_block_id_for_failure"""
271 """_add_block_id_for_failure"""
291 failure.blockID = blockID
272 failure.blockID = blockID
292 return failure
273 return failure
293
274
294
275
295 def _add_history(self, result, block=None):
276 def _add_history(self, result, block=None):
296 """Add block to the history"""
277 """Add block to the history"""
297
278
298 assert(block != None)
279 assert(block != None)
299 self.history.add_items([block])
280 self.history.add_items([block])
300 self.history_cursor += 1
281 self.history_cursor += 1
301
282
302 return result
283 return result
303
284
304
285
305 def get_history_previous(self, current_block):
286 def get_history_previous(self, current_block):
306 """ Returns previous history string and decrement history cursor.
287 """ Returns previous history string and decrement history cursor.
307 """
288 """
308 command = self.history.get_history_item(self.history_cursor - 1)
289 command = self.history.get_history_item(self.history_cursor - 1)
309
290
310 if command is not None:
291 if command is not None:
311 if(self.history_cursor+1 == len(self.history.input_cache)):
292 if(self.history_cursor+1 == len(self.history.input_cache)):
312 self.history.input_cache[self.history_cursor] = current_block
293 self.history.input_cache[self.history_cursor] = current_block
313 self.history_cursor -= 1
294 self.history_cursor -= 1
314 return command
295 return command
315
296
316
297
317 def get_history_next(self):
298 def get_history_next(self):
318 """ Returns next history string and increment history cursor.
299 """ Returns next history string and increment history cursor.
319 """
300 """
320 command = self.history.get_history_item(self.history_cursor+1)
301 command = self.history.get_history_item(self.history_cursor+1)
321
302
322 if command is not None:
303 if command is not None:
323 self.history_cursor += 1
304 self.history_cursor += 1
324 return command
305 return command
325
306
326 ###
307 ###
327 # Subclasses probably want to override these methods...
308 # Subclasses probably want to override these methods...
328 ###
309 ###
329
310
330 def update_cell_prompt(self, result, blockID=None):
311 def update_cell_prompt(self, result, blockID=None):
331 """Subclass may override to update the input prompt for a block.
312 """Subclass may override to update the input prompt for a block.
332
313
333 This method only really makes sens in asyncrhonous frontend.
314 This method only really makes sens in asyncrhonous frontend.
334 Since this method will be called as a
315 Since this method will be called as a
335 twisted.internet.defer.Deferred's callback, implementations should
316 twisted.internet.defer.Deferred's callback, implementations should
336 return result when finished.
317 return result when finished.
337 """
318 """
338
319
339 raise NotImplementedError
320 raise NotImplementedError
340
321
341
322
342 def render_result(self, result):
323 def render_result(self, result):
343 """Subclasses must override to render result.
324 """Subclasses must override to render result.
344
325
345 In asynchronous frontends, this method will be called as a
326 In asynchronous frontends, this method will be called as a
346 twisted.internet.defer.Deferred's callback. Implementations
327 twisted.internet.defer.Deferred's callback. Implementations
347 should thus return result when finished.
328 should thus return result when finished.
348 """
329 """
349
330
350 raise NotImplementedError
331 raise NotImplementedError
351
332
352
333
353 def render_error(self, failure):
334 def render_error(self, failure):
354 """Subclasses must override to render the failure.
335 """Subclasses must override to render the failure.
355
336
356 In asynchronous frontends, this method will be called as a
337 In asynchronous frontends, this method will be called as a
357 twisted.internet.defer.Deferred's callback. Implementations
338 twisted.internet.defer.Deferred's callback. Implementations
358 should thus return result when finished.
339 should thus return result when finished.
359 """
340 """
360
341
361 raise NotImplementedError
342 raise NotImplementedError
362
343
@@ -1,320 +1,333 b''
1 """
1 """
2 Base front end class for all line-oriented frontends, rather than
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
3 block-oriented.
4
4
5 Currently this focuses on synchronous frontends.
5 Currently this focuses on synchronous frontends.
6 """
6 """
7 __docformat__ = "restructuredtext en"
7 __docformat__ = "restructuredtext en"
8
8
9 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
10 # Copyright (C) 2008 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15
15
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import IPython
21 import IPython
22 import sys
22 import sys
23 import codeop
23 import codeop
24 import traceback
24 import traceback
25
25
26 from frontendbase import FrontEndBase
26 from frontendbase import FrontEndBase
27 from IPython.kernel.core.interpreter import Interpreter
27 from IPython.kernel.core.interpreter import Interpreter
28
28
29 def common_prefix(strings):
29 def common_prefix(strings):
30 """ Given a list of strings, return the common prefix between all
30 """ Given a list of strings, return the common prefix between all
31 these strings.
31 these strings.
32 """
32 """
33 ref = strings[0]
33 ref = strings[0]
34 prefix = ''
34 prefix = ''
35 for size in range(len(ref)):
35 for size in range(len(ref)):
36 test_prefix = ref[:size+1]
36 test_prefix = ref[:size+1]
37 for string in strings[1:]:
37 for string in strings[1:]:
38 if not string.startswith(test_prefix):
38 if not string.startswith(test_prefix):
39 return prefix
39 return prefix
40 prefix = test_prefix
40 prefix = test_prefix
41
41
42 return prefix
42 return prefix
43
43
44 #-------------------------------------------------------------------------------
44 #-------------------------------------------------------------------------------
45 # Base class for the line-oriented front ends
45 # Base class for the line-oriented front ends
46 #-------------------------------------------------------------------------------
46 #-------------------------------------------------------------------------------
47 class LineFrontEndBase(FrontEndBase):
47 class LineFrontEndBase(FrontEndBase):
48 """ Concrete implementation of the FrontEndBase class. This is meant
48 """ Concrete implementation of the FrontEndBase class. This is meant
49 to be the base class behind all the frontend that are line-oriented,
49 to be the base class behind all the frontend that are line-oriented,
50 rather than block-oriented.
50 rather than block-oriented.
51 """
51 """
52
52
53 # We need to keep the prompt number, to be able to increment
53 # We need to keep the prompt number, to be able to increment
54 # it when there is an exception.
54 # it when there is an exception.
55 prompt_number = 1
55 prompt_number = 1
56
56
57 # We keep a reference to the last result: it helps testing and
57 # We keep a reference to the last result: it helps testing and
58 # programatic control of the frontend.
58 # programatic control of the frontend.
59 last_result = dict(number=0)
59 last_result = dict(number=0)
60
60
61 # The input buffer being edited
61 # The input buffer being edited
62 input_buffer = ''
62 input_buffer = ''
63
63
64 # Set to true for debug output
64 # Set to true for debug output
65 debug = False
65 debug = False
66
66
67 # A banner to print at startup
67 # A banner to print at startup
68 banner = None
68 banner = None
69
69
70 #--------------------------------------------------------------------------
70 #--------------------------------------------------------------------------
71 # FrontEndBase interface
71 # FrontEndBase interface
72 #--------------------------------------------------------------------------
72 #--------------------------------------------------------------------------
73
73
74 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
74 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
75 if shell is None:
75 if shell is None:
76 shell = Interpreter()
76 shell = Interpreter()
77 FrontEndBase.__init__(self, shell=shell, history=history)
77 FrontEndBase.__init__(self, shell=shell, history=history)
78
78
79 if banner is not None:
79 if banner is not None:
80 self.banner = banner
80 self.banner = banner
81
81
82 def start(self):
82 def start(self):
83 """ Put the frontend in a state where it is ready for user
83 """ Put the frontend in a state where it is ready for user
84 interaction.
84 interaction.
85 """
85 """
86 if self.banner is not None:
86 if self.banner is not None:
87 self.write(self.banner, refresh=False)
87 self.write(self.banner, refresh=False)
88
88
89 self.new_prompt(self.input_prompt_template.substitute(number=1))
89 self.new_prompt(self.input_prompt_template.substitute(number=1))
90
90
91
91
92 def complete(self, line):
92 def complete(self, line):
93 """Complete line in engine's user_ns
93 """Complete line in engine's user_ns
94
94
95 Parameters
95 Parameters
96 ----------
96 ----------
97 line : string
97 line : string
98
98
99 Result
99 Result
100 ------
100 ------
101 The replacement for the line and the list of possible completions.
101 The replacement for the line and the list of possible completions.
102 """
102 """
103 completions = self.shell.complete(line)
103 completions = self.shell.complete(line)
104 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
104 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
105 if completions:
105 if completions:
106 prefix = common_prefix(completions)
106 prefix = common_prefix(completions)
107 residual = complete_sep.split(line)[:-1]
107 residual = complete_sep.split(line)[:-1]
108 line = line[:-len(residual)] + prefix
108 line = line[:-len(residual)] + prefix
109 return line, completions
109 return line, completions
110
110
111
111
112 def render_result(self, result):
112 def render_result(self, result):
113 """ Frontend-specific rendering of the result of a calculation
113 """ Frontend-specific rendering of the result of a calculation
114 that has been sent to an engine.
114 that has been sent to an engine.
115 """
115 """
116 if 'stdout' in result and result['stdout']:
116 if 'stdout' in result and result['stdout']:
117 self.write('\n' + result['stdout'])
117 self.write('\n' + result['stdout'])
118 if 'display' in result and result['display']:
118 if 'display' in result and result['display']:
119 self.write("%s%s\n" % (
119 self.write("%s%s\n" % (
120 self.output_prompt_template.substitute(
120 self.output_prompt_template.substitute(
121 number=result['number']),
121 number=result['number']),
122 result['display']['pprint']
122 result['display']['pprint']
123 ) )
123 ) )
124
124
125
125
126 def render_error(self, failure):
126 def render_error(self, failure):
127 """ Frontend-specific rendering of error.
127 """ Frontend-specific rendering of error.
128 """
128 """
129 self.write('\n\n'+str(failure)+'\n\n')
129 self.write('\n\n'+str(failure)+'\n\n')
130 return failure
130 return failure
131
131
132
132
133 def is_complete(self, string):
133 def is_complete(self, string):
134 """ Check if a string forms a complete, executable set of
134 """ Check if a string forms a complete, executable set of
135 commands.
135 commands.
136
136
137 For the line-oriented frontend, multi-line code is not executed
137 For the line-oriented frontend, multi-line code is not executed
138 as soon as it is complete: the users has to enter two line
138 as soon as it is complete: the users has to enter two line
139 returns.
139 returns.
140 """
140 """
141 if string in ('', '\n'):
141 if string in ('', '\n'):
142 # Prefiltering, eg through ipython0, may return an empty
142 # Prefiltering, eg through ipython0, may return an empty
143 # string although some operations have been accomplished. We
143 # string although some operations have been accomplished. We
144 # thus want to consider an empty string as a complete
144 # thus want to consider an empty string as a complete
145 # statement.
145 # statement.
146 return True
146 return True
147 elif ( len(self.input_buffer.split('\n'))>2
147 elif ( len(self.input_buffer.split('\n'))>2
148 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
148 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
149 return False
149 return False
150 else:
150 else:
151 self.capture_output()
151 self.capture_output()
152 try:
152 try:
153 # Add line returns here, to make sure that the statement is
153 # Add line returns here, to make sure that the statement is
154 # complete.
154 # complete.
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 "<string>", "exec")
156 "<string>", "exec")
157 self.release_output()
157 self.release_output()
158 except Exception, e:
158 except Exception, e:
159 # XXX: Hack: return True so that the
159 # XXX: Hack: return True so that the
160 # code gets executed and the error captured.
160 # code gets executed and the error captured.
161 is_complete = True
161 is_complete = True
162 return is_complete
162 return is_complete
163
163
164
164
165 def write(self, string, refresh=True):
165 def write(self, string, refresh=True):
166 """ Write some characters to the display.
166 """ Write some characters to the display.
167
167
168 Subclass should overide this method.
168 Subclass should overide this method.
169
169
170 The refresh keyword argument is used in frontends with an
170 The refresh keyword argument is used in frontends with an
171 event loop, to choose whether the write should trigget an UI
171 event loop, to choose whether the write should trigget an UI
172 refresh, and thus be syncrhonous, or not.
172 refresh, and thus be syncrhonous, or not.
173 """
173 """
174 print >>sys.__stderr__, string
174 print >>sys.__stderr__, string
175
175
176
176
177 def execute(self, python_string, raw_string=None):
177 def execute(self, python_string, raw_string=None):
178 """ Stores the raw_string in the history, and sends the
178 """ Stores the raw_string in the history, and sends the
179 python string to the interpreter.
179 python string to the interpreter.
180 """
180 """
181 if raw_string is None:
181 if raw_string is None:
182 raw_string = python_string
182 raw_string = python_string
183 # Create a false result, in case there is an exception
183 # Create a false result, in case there is an exception
184 self.last_result = dict(number=self.prompt_number)
184 self.last_result = dict(number=self.prompt_number)
185
186 ## try:
187 ## self.history.input_cache[-1] = raw_string.rstrip()
188 ## result = self.shell.execute(python_string)
189 ## self.last_result = result
190 ## self.render_result(result)
191 ## except:
192 ## self.show_traceback()
193 ## finally:
194 ## self.after_execute()
195
196 try:
185 try:
197 try:
186 self.history.input_cache[-1] = raw_string.rstrip()
198 self.history.input_cache[-1] = raw_string.rstrip()
187 result = self.shell.execute(python_string)
199 result = self.shell.execute(python_string)
188 self.last_result = result
200 self.last_result = result
189 self.render_result(result)
201 self.render_result(result)
190 except:
202 except:
191 self.show_traceback()
203 self.show_traceback()
192 finally:
204 finally:
193 self.after_execute()
205 self.after_execute()
194
206
207
195 #--------------------------------------------------------------------------
208 #--------------------------------------------------------------------------
196 # LineFrontEndBase interface
209 # LineFrontEndBase interface
197 #--------------------------------------------------------------------------
210 #--------------------------------------------------------------------------
198
211
199 def prefilter_input(self, string):
212 def prefilter_input(self, string):
200 """ Prefilter the input to turn it in valid python.
213 """ Prefilter the input to turn it in valid python.
201 """
214 """
202 string = string.replace('\r\n', '\n')
215 string = string.replace('\r\n', '\n')
203 string = string.replace('\t', 4*' ')
216 string = string.replace('\t', 4*' ')
204 # Clean the trailing whitespace
217 # Clean the trailing whitespace
205 string = '\n'.join(l.rstrip() for l in string.split('\n'))
218 string = '\n'.join(l.rstrip() for l in string.split('\n'))
206 return string
219 return string
207
220
208
221
209 def after_execute(self):
222 def after_execute(self):
210 """ All the operations required after an execution to put the
223 """ All the operations required after an execution to put the
211 terminal back in a shape where it is usable.
224 terminal back in a shape where it is usable.
212 """
225 """
213 self.prompt_number += 1
226 self.prompt_number += 1
214 self.new_prompt(self.input_prompt_template.substitute(
227 self.new_prompt(self.input_prompt_template.substitute(
215 number=(self.last_result['number'] + 1)))
228 number=(self.last_result['number'] + 1)))
216 # Start a new empty history entry
229 # Start a new empty history entry
217 self._add_history(None, '')
230 self._add_history(None, '')
218 self.history_cursor = len(self.history.input_cache) - 1
231 self.history_cursor = len(self.history.input_cache) - 1
219
232
220
233
221 def complete_current_input(self):
234 def complete_current_input(self):
222 """ Do code completion on current line.
235 """ Do code completion on current line.
223 """
236 """
224 if self.debug:
237 if self.debug:
225 print >>sys.__stdout__, "complete_current_input",
238 print >>sys.__stdout__, "complete_current_input",
226 line = self.input_buffer
239 line = self.input_buffer
227 new_line, completions = self.complete(line)
240 new_line, completions = self.complete(line)
228 if len(completions)>1:
241 if len(completions)>1:
229 self.write_completion(completions, new_line=new_line)
242 self.write_completion(completions, new_line=new_line)
230 elif not line == new_line:
243 elif not line == new_line:
231 self.input_buffer = new_line
244 self.input_buffer = new_line
232 if self.debug:
245 if self.debug:
233 print >>sys.__stdout__, 'line', line
246 print >>sys.__stdout__, 'line', line
234 print >>sys.__stdout__, 'new_line', new_line
247 print >>sys.__stdout__, 'new_line', new_line
235 print >>sys.__stdout__, completions
248 print >>sys.__stdout__, completions
236
249
237
250
238 def get_line_width(self):
251 def get_line_width(self):
239 """ Return the width of the line in characters.
252 """ Return the width of the line in characters.
240 """
253 """
241 return 80
254 return 80
242
255
243
256
244 def write_completion(self, possibilities, new_line=None):
257 def write_completion(self, possibilities, new_line=None):
245 """ Write the list of possible completions.
258 """ Write the list of possible completions.
246
259
247 new_line is the completed input line that should be displayed
260 new_line is the completed input line that should be displayed
248 after the completion are writen. If None, the input_buffer
261 after the completion are writen. If None, the input_buffer
249 before the completion is used.
262 before the completion is used.
250 """
263 """
251 if new_line is None:
264 if new_line is None:
252 new_line = self.input_buffer
265 new_line = self.input_buffer
253
266
254 self.write('\n')
267 self.write('\n')
255 max_len = len(max(possibilities, key=len)) + 1
268 max_len = len(max(possibilities, key=len)) + 1
256
269
257 # Now we check how much symbol we can put on a line...
270 # Now we check how much symbol we can put on a line...
258 chars_per_line = self.get_line_width()
271 chars_per_line = self.get_line_width()
259 symbols_per_line = max(1, chars_per_line/max_len)
272 symbols_per_line = max(1, chars_per_line/max_len)
260
273
261 pos = 1
274 pos = 1
262 buf = []
275 buf = []
263 for symbol in possibilities:
276 for symbol in possibilities:
264 if pos < symbols_per_line:
277 if pos < symbols_per_line:
265 buf.append(symbol.ljust(max_len))
278 buf.append(symbol.ljust(max_len))
266 pos += 1
279 pos += 1
267 else:
280 else:
268 buf.append(symbol.rstrip() + '\n')
281 buf.append(symbol.rstrip() + '\n')
269 pos = 1
282 pos = 1
270 self.write(''.join(buf))
283 self.write(''.join(buf))
271 self.new_prompt(self.input_prompt_template.substitute(
284 self.new_prompt(self.input_prompt_template.substitute(
272 number=self.last_result['number'] + 1))
285 number=self.last_result['number'] + 1))
273 self.input_buffer = new_line
286 self.input_buffer = new_line
274
287
275
288
276 def new_prompt(self, prompt):
289 def new_prompt(self, prompt):
277 """ Prints a prompt and starts a new editing buffer.
290 """ Prints a prompt and starts a new editing buffer.
278
291
279 Subclasses should use this method to make sure that the
292 Subclasses should use this method to make sure that the
280 terminal is put in a state favorable for a new line
293 terminal is put in a state favorable for a new line
281 input.
294 input.
282 """
295 """
283 self.input_buffer = ''
296 self.input_buffer = ''
284 self.write(prompt)
297 self.write(prompt)
285
298
286
299
287 #--------------------------------------------------------------------------
300 #--------------------------------------------------------------------------
288 # Private API
301 # Private API
289 #--------------------------------------------------------------------------
302 #--------------------------------------------------------------------------
290
303
291 def _on_enter(self):
304 def _on_enter(self):
292 """ Called when the return key is pressed in a line editing
305 """ Called when the return key is pressed in a line editing
293 buffer.
306 buffer.
294 """
307 """
295 current_buffer = self.input_buffer
308 current_buffer = self.input_buffer
296 cleaned_buffer = self.prefilter_input(current_buffer)
309 cleaned_buffer = self.prefilter_input(current_buffer)
297 if self.is_complete(cleaned_buffer):
310 if self.is_complete(cleaned_buffer):
298 self.execute(cleaned_buffer, raw_string=current_buffer)
311 self.execute(cleaned_buffer, raw_string=current_buffer)
299 else:
312 else:
300 self.input_buffer += self._get_indent_string(
313 self.input_buffer += self._get_indent_string(
301 current_buffer[:-1])
314 current_buffer[:-1])
302 if len(current_buffer.split('\n')) == 2:
315 if len(current_buffer.split('\n')) == 2:
303 self.input_buffer += '\t\t'
316 self.input_buffer += '\t\t'
304 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
317 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
305 self.input_buffer += '\t'
318 self.input_buffer += '\t'
306
319
307
320
308 def _get_indent_string(self, string):
321 def _get_indent_string(self, string):
309 """ Return the string of whitespace that prefixes a line. Used to
322 """ Return the string of whitespace that prefixes a line. Used to
310 add the right amount of indendation when creating a new line.
323 add the right amount of indendation when creating a new line.
311 """
324 """
312 string = string.replace('\t', ' '*4)
325 string = string.replace('\t', ' '*4)
313 string = string.split('\n')[-1]
326 string = string.split('\n')[-1]
314 indent_chars = len(string) - len(string.lstrip())
327 indent_chars = len(string) - len(string.lstrip())
315 indent_string = '\t'*(indent_chars // 4) + \
328 indent_string = '\t'*(indent_chars // 4) + \
316 ' '*(indent_chars % 4)
329 ' '*(indent_chars % 4)
317
330
318 return indent_string
331 return indent_string
319
332
320
333
@@ -1,230 +1,246 b''
1 """
1 """
2 Frontend class that uses IPython0 to prefilter the inputs.
2 Frontend class that uses IPython0 to prefilter the inputs.
3
3
4 Using the IPython0 mechanism gives us access to the magics.
4 Using the IPython0 mechanism gives us access to the magics.
5
5
6 This is a transitory class, used here to do the transition between
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
10 refactoring.
11 """
11 """
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25
25
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 from linefrontendbase import LineFrontEndBase, common_prefix
27 from frontendbase import FrontEndBase
27 from frontendbase import FrontEndBase
28
28
29 from IPython.ipmaker import make_IPython
29 from IPython.ipmaker import make_IPython
30 from IPython.ipapi import IPApi
30 from IPython.ipapi import IPApi
31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
32
32
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34
34
35 from IPython.genutils import Term
35 from IPython.genutils import Term
36 import pydoc
36 import pydoc
37 import os
37 import os
38 import sys
38 import sys
39
39
40
40
41 def mk_system_call(system_call_function, command):
41 def mk_system_call(system_call_function, command):
42 """ given a os.system replacement, and a leading string command,
42 """ given a os.system replacement, and a leading string command,
43 returns a function that will execute the command with the given
43 returns a function that will execute the command with the given
44 argument string.
44 argument string.
45 """
45 """
46 def my_system_call(args):
46 def my_system_call(args):
47 system_call_function("%s %s" % (command, args))
47 system_call_function("%s %s" % (command, args))
48 return my_system_call
48 return my_system_call
49
49
50 #-------------------------------------------------------------------------------
50 #-------------------------------------------------------------------------------
51 # Frontend class using ipython0 to do the prefiltering.
51 # Frontend class using ipython0 to do the prefiltering.
52 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
53 class PrefilterFrontEnd(LineFrontEndBase):
53 class PrefilterFrontEnd(LineFrontEndBase):
54 """ Class that uses ipython0 to do prefilter the input, do the
54 """ Class that uses ipython0 to do prefilter the input, do the
55 completion and the magics.
55 completion and the magics.
56
56
57 The core trick is to use an ipython0 instance to prefilter the
57 The core trick is to use an ipython0 instance to prefilter the
58 input, and share the namespace between the interpreter instance used
58 input, and share the namespace between the interpreter instance used
59 to execute the statements and the ipython0 used for code
59 to execute the statements and the ipython0 used for code
60 completion...
60 completion...
61 """
61 """
62
62
63 debug = False
63 debug = False
64
64
65 def __init__(self, ipython0=None, *args, **kwargs):
65 def __init__(self, ipython0=None, *args, **kwargs):
66 """ Parameters:
66 """ Parameters:
67 -----------
67 -----------
68
68
69 ipython0: an optional ipython0 instance to use for command
69 ipython0: an optional ipython0 instance to use for command
70 prefiltering and completion.
70 prefiltering and completion.
71 """
71 """
72 LineFrontEndBase.__init__(self, *args, **kwargs)
72 LineFrontEndBase.__init__(self, *args, **kwargs)
73 self.shell.output_trap = RedirectorOutputTrap(
73 self.shell.output_trap = RedirectorOutputTrap(
74 out_callback=self.write,
74 out_callback=self.write,
75 err_callback=self.write,
75 err_callback=self.write,
76 )
76 )
77 self.shell.traceback_trap = SyncTracebackTrap(
77 self.shell.traceback_trap = SyncTracebackTrap(
78 formatters=self.shell.traceback_trap.formatters,
78 formatters=self.shell.traceback_trap.formatters,
79 )
79 )
80
80
81 # Start the ipython0 instance:
81 # Start the ipython0 instance:
82 self.save_output_hooks()
82 self.save_output_hooks()
83 if ipython0 is None:
83 if ipython0 is None:
84 # Instanciate an IPython0 interpreter to be able to use the
84 # Instanciate an IPython0 interpreter to be able to use the
85 # prefiltering.
85 # prefiltering.
86 # XXX: argv=[] is a bit bold.
86 # XXX: argv=[] is a bit bold.
87 ipython0 = make_IPython(argv=[],
87 ipython0 = make_IPython(argv=[],
88 user_ns=self.shell.user_ns,
88 user_ns=self.shell.user_ns,
89 user_global_ns=self.shell.user_global_ns)
89 user_global_ns=self.shell.user_global_ns)
90 self.ipython0 = ipython0
90 self.ipython0 = ipython0
91 # Set the pager:
91 # Set the pager:
92 self.ipython0.set_hook('show_in_pager',
92 self.ipython0.set_hook('show_in_pager',
93 lambda s, string: self.write("\n" + string))
93 lambda s, string: self.write("\n" + string))
94 self.ipython0.write = self.write
94 self.ipython0.write = self.write
95 self._ip = _ip = IPApi(self.ipython0)
95 self._ip = _ip = IPApi(self.ipython0)
96 # Make sure the raw system call doesn't get called, as we don't
96 # Make sure the raw system call doesn't get called, as we don't
97 # have a stdin accessible.
97 # have a stdin accessible.
98 self._ip.system = self.system_call
98 self._ip.system = self.system_call
99 # XXX: Muck around with magics so that they work better
99 # XXX: Muck around with magics so that they work better
100 # in our environment
100 # in our environment
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
102 'ls -CF')
102 'ls -CF')
103 # And now clean up the mess created by ipython0
103 # And now clean up the mess created by ipython0
104 self.release_output()
104 self.release_output()
105
105
106
106
107 if not 'banner' in kwargs and self.banner is None:
107 if not 'banner' in kwargs and self.banner is None:
108 self.banner = self.ipython0.BANNER + """
108 self.banner = self.ipython0.BANNER + """
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
110
110
111 self.start()
111 self.start()
112
112
113 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
114 # FrontEndBase interface
114 # FrontEndBase interface
115 #--------------------------------------------------------------------------
115 #--------------------------------------------------------------------------
116
116
117 def show_traceback(self):
117 def show_traceback(self):
118 """ Use ipython0 to capture the last traceback and display it.
118 """ Use ipython0 to capture the last traceback and display it.
119 """
119 """
120 self.capture_output()
120 self.capture_output()
121 self.ipython0.showtraceback(tb_offset=-1)
121 self.ipython0.showtraceback(tb_offset=-1)
122 self.release_output()
122 self.release_output()
123
123
124
124
125 def execute(self, python_string, raw_string=None):
125 def execute(self, python_string, raw_string=None):
126 if self.debug:
126 if self.debug:
127 print 'Executing Python code:', repr(python_string)
127 print 'Executing Python code:', repr(python_string)
128 self.capture_output()
128 self.capture_output()
129 LineFrontEndBase.execute(self, python_string,
129 LineFrontEndBase.execute(self, python_string,
130 raw_string=raw_string)
130 raw_string=raw_string)
131 self.release_output()
131 self.release_output()
132
132
133
133
134 def save_output_hooks(self):
134 def save_output_hooks(self):
135 """ Store all the output hooks we can think of, to be able to
135 """ Store all the output hooks we can think of, to be able to
136 restore them.
136 restore them.
137
137
138 We need to do this early, as starting the ipython0 instance will
138 We need to do this early, as starting the ipython0 instance will
139 screw ouput hooks.
139 screw ouput hooks.
140 """
140 """
141 self.__old_cout_write = Term.cout.write
141 self.__old_cout_write = Term.cout.write
142 self.__old_cerr_write = Term.cerr.write
142 self.__old_cerr_write = Term.cerr.write
143 self.__old_stdout = sys.stdout
143 self.__old_stdout = sys.stdout
144 self.__old_stderr= sys.stderr
144 self.__old_stderr= sys.stderr
145 self.__old_help_output = pydoc.help.output
145 self.__old_help_output = pydoc.help.output
146 self.__old_display_hook = sys.displayhook
146 self.__old_display_hook = sys.displayhook
147
147
148
148
149 def capture_output(self):
149 def capture_output(self):
150 """ Capture all the output mechanisms we can think of.
150 """ Capture all the output mechanisms we can think of.
151 """
151 """
152 self.save_output_hooks()
152 self.save_output_hooks()
153 Term.cout.write = self.write
153 Term.cout.write = self.write
154 Term.cerr.write = self.write
154 Term.cerr.write = self.write
155 sys.stdout = Term.cout
155 sys.stdout = Term.cout
156 sys.stderr = Term.cerr
156 sys.stderr = Term.cerr
157 pydoc.help.output = self.shell.output_trap.out
157 pydoc.help.output = self.shell.output_trap.out
158
158
159
159
160 def release_output(self):
160 def release_output(self):
161 """ Release all the different captures we have made.
161 """ Release all the different captures we have made.
162 """
162 """
163 Term.cout.write = self.__old_cout_write
163 Term.cout.write = self.__old_cout_write
164 Term.cerr.write = self.__old_cerr_write
164 Term.cerr.write = self.__old_cerr_write
165 sys.stdout = self.__old_stdout
165 sys.stdout = self.__old_stdout
166 sys.stderr = self.__old_stderr
166 sys.stderr = self.__old_stderr
167 pydoc.help.output = self.__old_help_output
167 pydoc.help.output = self.__old_help_output
168 sys.displayhook = self.__old_display_hook
168 sys.displayhook = self.__old_display_hook
169
169
170
170
171 def complete(self, line):
171 def complete(self, line):
172 # FIXME: This should be factored out in the linefrontendbase
172 # FIXME: This should be factored out in the linefrontendbase
173 # method.
173 # method.
174 word = line.split('\n')[-1].split(' ')[-1]
174 word = line.split('\n')[-1].split(' ')[-1]
175 completions = self.ipython0.complete(word)
175 completions = self.ipython0.complete(word)
176 # FIXME: The proper sort should be done in the complete method.
176 # FIXME: The proper sort should be done in the complete method.
177 key = lambda x: x.replace('_', '')
177 key = lambda x: x.replace('_', '')
178 completions.sort(key=key)
178 completions.sort(key=key)
179 if completions:
179 if completions:
180 prefix = common_prefix(completions)
180 prefix = common_prefix(completions)
181 line = line[:-len(word)] + prefix
181 line = line[:-len(word)] + prefix
182 return line, completions
182 return line, completions
183
183
184
184
185 #--------------------------------------------------------------------------
185 #--------------------------------------------------------------------------
186 # LineFrontEndBase interface
186 # LineFrontEndBase interface
187 #--------------------------------------------------------------------------
187 #--------------------------------------------------------------------------
188
188
189 def prefilter_input(self, input_string):
189 def prefilter_input(self, input_string):
190 """ Using IPython0 to prefilter the commands to turn them
190 """ Using IPython0 to prefilter the commands to turn them
191 in executable statements that are valid Python strings.
191 in executable statements that are valid Python strings.
192 """
192 """
193 input_string = LineFrontEndBase.prefilter_input(self, input_string)
193 input_string = LineFrontEndBase.prefilter_input(self, input_string)
194 filtered_lines = []
194 filtered_lines = []
195 # The IPython0 prefilters sometime produce output. We need to
195 # The IPython0 prefilters sometime produce output. We need to
196 # capture it.
196 # capture it.
197 self.capture_output()
197 self.capture_output()
198 self.last_result = dict(number=self.prompt_number)
198 self.last_result = dict(number=self.prompt_number)
199
200 ## try:
201 ## for line in input_string.split('\n'):
202 ## filtered_lines.append(
203 ## self.ipython0.prefilter(line, False).rstrip())
204 ## except:
205 ## # XXX: probably not the right thing to do.
206 ## self.ipython0.showsyntaxerror()
207 ## self.after_execute()
208 ## finally:
209 ## self.release_output()
210
211
212 try:
199 try:
213 try:
200 for line in input_string.split('\n'):
214 for line in input_string.split('\n'):
201 filtered_lines.append(
215 filtered_lines.append(
202 self.ipython0.prefilter(line, False).rstrip())
216 self.ipython0.prefilter(line, False).rstrip())
203 except:
217 except:
204 # XXX: probably not the right thing to do.
218 # XXX: probably not the right thing to do.
205 self.ipython0.showsyntaxerror()
219 self.ipython0.showsyntaxerror()
206 self.after_execute()
220 self.after_execute()
207 finally:
221 finally:
208 self.release_output()
222 self.release_output()
209
223
224
225
210 # Clean up the trailing whitespace, to avoid indentation errors
226 # Clean up the trailing whitespace, to avoid indentation errors
211 filtered_string = '\n'.join(filtered_lines)
227 filtered_string = '\n'.join(filtered_lines)
212 return filtered_string
228 return filtered_string
213
229
214
230
215 #--------------------------------------------------------------------------
231 #--------------------------------------------------------------------------
216 # PrefilterFrontEnd interface
232 # PrefilterFrontEnd interface
217 #--------------------------------------------------------------------------
233 #--------------------------------------------------------------------------
218
234
219 def system_call(self, command_string):
235 def system_call(self, command_string):
220 """ Allows for frontend to define their own system call, to be
236 """ Allows for frontend to define their own system call, to be
221 able capture output and redirect input.
237 able capture output and redirect input.
222 """
238 """
223 return os.system(command_string)
239 return os.system(command_string)
224
240
225
241
226 def do_exit(self):
242 def do_exit(self):
227 """ Exit the shell, cleanup and save the history.
243 """ Exit the shell, cleanup and save the history.
228 """
244 """
229 self.ipython0.atexit_operations()
245 self.ipython0.atexit_operations()
230
246
@@ -1,155 +1,32 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2 """
3 """This file contains unittests for the frontendbase module."""
3 Test the basic functionality of frontendbase.
4 """
4
5
5 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
6
7
7 #---------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
9 #
10 #
10 # 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
11 # the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17
18 import unittest
19
20 try:
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
24 except ImportError:
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
28 from IPython.testing.decorators import skip
29
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
31 """FrontEndBase subclass for checking callbacks"""
32 def __init__(self, engine=None, history=None):
33 super(FrontEndCallbackChecker, self).__init__(engine=engine,
34 history=history)
35 self.updateCalled = False
36 self.renderResultCalled = False
37 self.renderErrorCalled = False
38
39 def update_cell_prompt(self, result, blockID=None):
40 self.updateCalled = True
41 return result
42
43 def render_result(self, result):
44 self.renderResultCalled = True
45 return result
46
47
48 def render_error(self, failure):
49 self.renderErrorCalled = True
50 return failure
51
52
53
54
55 class TestAsyncFrontendBase(unittest.TestCase):
56 def setUp(self):
57 """Setup the EngineService and FrontEndBase"""
58
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
60
61 def test_implements_IFrontEnd(self):
62 assert(frontendbase.IFrontEnd.implementedBy(
63 AsyncFrontEndBase))
64
65 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
67
68 block = """def test(a):"""
69
70 assert(self.fb.is_complete(block) == False)
71
72 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
74
75 block = """def test(a): pass"""
76
77 assert(self.fb.is_complete(block))
78
79 block = """a=3"""
80
14
81 assert(self.fb.is_complete(block))
15 from IPython.frontend.frontendbase import FrontEndBase
82
16
83 def test_blockID_added_to_result(self):
17 def test_iscomplete():
84 block = """3+3"""
18 """ Check that is_complete works.
85
86 d = self.fb.execute(block, blockID='TEST_ID')
87
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
89
90 def test_blockID_added_to_failure(self):
91 block = "raise Exception()"
92
93 d = self.fb.execute(block,blockID='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
95
96 def checkBlockID(self, result, expected=""):
97 assert(result['blockID'] == expected)
98
99
100 def checkFailureID(self, failure, expected=""):
101 assert(failure.blockID == expected)
102
103
104 def test_callbacks_added_to_execute(self):
105 """test that
106 update_cell_prompt
107 render_result
108
109 are added to execute request
110 """
19 """
111
20 f = FrontEndBase()
112 d = self.fb.execute("10+10")
21 assert f.is_complete('(a + a)')
113 d.addCallback(self.checkCallbacks)
22 assert not f.is_complete('(a + a')
114
23 assert f.is_complete('1')
115 def checkCallbacks(self, result):
24 assert not f.is_complete('1 + ')
116 assert(self.fb.updateCalled)
25 assert not f.is_complete('1 + \n\n')
117 assert(self.fb.renderResultCalled)
26 assert f.is_complete('if True:\n print 1\n')
118
27 assert not f.is_complete('if True:\n print 1')
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
28 assert f.is_complete('def f():\n print 1\n')
120 def test_error_callback_added_to_execute(self):
29
121 """test that render_error called on execution error"""
30 if __name__ == '__main__':
122
31 test_iscomplete()
123 d = self.fb.execute("raise Exception()")
124 d.addCallback(self.checkRenderError)
125
126 def checkRenderError(self, result):
127 assert(self.fb.renderErrorCalled)
128
129 def test_history_returns_expected_block(self):
130 """Make sure history browsing doesn't fail"""
131
132 blocks = ["a=1","a=2","a=3"]
133 for b in blocks:
134 d = self.fb.execute(b)
135
136 # d is now the deferred for the last executed block
137 d.addCallback(self.historyTests, blocks)
138
139
140 def historyTests(self, result, blocks):
141 """historyTests"""
142
143 assert(len(blocks) >= 3)
144 assert(self.fb.get_history_previous("") == blocks[-2])
145 assert(self.fb.get_history_previous("") == blocks[-3])
146 assert(self.fb.get_history_next() == blocks[-2])
147
148
149 def test_history_returns_none_at_startup(self):
150 """test_history_returns_none_at_startup"""
151
152 assert(self.fb.get_history_previous("")==None)
153 assert(self.fb.get_history_next()==None)
154
155
32
@@ -1,34 +1,27 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 zope.interface mock. If zope is installed, this module provides a zope
4 zope.interface mock. If zope is installed, this module provides a zope
5 interface classes, if not it provides mocks for them.
5 interface classes, if not it provides mocks for them.
6
6
7 Classes provided:
7 Classes provided:
8 Interface, Attribute, implements, classProvides
8 Interface, Attribute, implements, classProvides
9 """
9 """
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22 import string
23 import uuid
24 import _ast
25
26 try:
19 try:
27 from zope.interface import Interface, Attribute, implements, classProvides
20 from zope.interface import Interface, Attribute, implements, classProvides
28 except ImportError:
21 except ImportError:
29 #zope.interface is not available
22 #zope.interface is not available
30 Interface = object
23 Interface = object
31 def Attribute(name, doc): pass
24 def Attribute(name, doc): pass
32 def implements(interface): pass
25 def implements(interface): pass
33 def classProvides(interface): pass
26 def classProvides(interface): pass
34
27
@@ -1,143 +1,141 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_contexts -*-
2 # -*- test-case-name: IPython.kernel.test.test_contexts -*-
3 """Context managers for IPython.
3 """Context managers for IPython.
4
4
5 Python 2.5 introduced the `with` statement, which is based on the context
5 Python 2.5 introduced the `with` statement, which is based on the context
6 manager protocol. This module offers a few context managers for common cases,
6 manager protocol. This module offers a few context managers for common cases,
7 which can also be useful as templates for writing new, application-specific
7 which can also be useful as templates for writing new, application-specific
8 managers.
8 managers.
9 """
9 """
10
10
11 from __future__ import with_statement
12
13 __docformat__ = "restructuredtext en"
11 __docformat__ = "restructuredtext en"
14
12
15 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
17 #
15 #
18 # 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
19 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
21
19
22 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
23 # Imports
21 # Imports
24 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
25
23
26 import linecache
24 import linecache
27 import sys
25 import sys
28
26
29 from twisted.internet.error import ConnectionRefusedError
27 from twisted.internet.error import ConnectionRefusedError
30
28
31 from IPython.ultraTB import _fixed_getinnerframes, findsource
29 from IPython.ultraTB import _fixed_getinnerframes, findsource
32 from IPython import ipapi
30 from IPython import ipapi
33
31
34 from IPython.kernel import error
32 from IPython.kernel import error
35
33
36 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
37 # Utility functions needed by all context managers.
35 # Utility functions needed by all context managers.
38 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
39
37
40 def remote():
38 def remote():
41 """Raises a special exception meant to be caught by context managers.
39 """Raises a special exception meant to be caught by context managers.
42 """
40 """
43 m = 'Special exception to stop local execution of parallel code.'
41 m = 'Special exception to stop local execution of parallel code.'
44 raise error.StopLocalExecution(m)
42 raise error.StopLocalExecution(m)
45
43
46
44
47 def strip_whitespace(source,require_remote=True):
45 def strip_whitespace(source,require_remote=True):
48 """strip leading whitespace from input source.
46 """strip leading whitespace from input source.
49
47
50 :Parameters:
48 :Parameters:
51
49
52 """
50 """
53 remote_mark = 'remote()'
51 remote_mark = 'remote()'
54 # Expand tabs to avoid any confusion.
52 # Expand tabs to avoid any confusion.
55 wsource = [l.expandtabs(4) for l in source]
53 wsource = [l.expandtabs(4) for l in source]
56 # Detect the indentation level
54 # Detect the indentation level
57 done = False
55 done = False
58 for line in wsource:
56 for line in wsource:
59 if line.isspace():
57 if line.isspace():
60 continue
58 continue
61 for col,char in enumerate(line):
59 for col,char in enumerate(line):
62 if char != ' ':
60 if char != ' ':
63 done = True
61 done = True
64 break
62 break
65 if done:
63 if done:
66 break
64 break
67 # Now we know how much leading space there is in the code. Next, we
65 # Now we know how much leading space there is in the code. Next, we
68 # extract up to the first line that has less indentation.
66 # extract up to the first line that has less indentation.
69 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
67 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
70 # detect triple quoted strings that may have flush left text.
68 # detect triple quoted strings that may have flush left text.
71 for lno,line in enumerate(wsource):
69 for lno,line in enumerate(wsource):
72 lead = line[:col]
70 lead = line[:col]
73 if lead.isspace():
71 if lead.isspace():
74 continue
72 continue
75 else:
73 else:
76 if not lead.lstrip().startswith('#'):
74 if not lead.lstrip().startswith('#'):
77 break
75 break
78 # The real 'with' source is up to lno
76 # The real 'with' source is up to lno
79 src_lines = [l[col:] for l in wsource[:lno+1]]
77 src_lines = [l[col:] for l in wsource[:lno+1]]
80
78
81 # Finally, check that the source's first non-comment line begins with the
79 # Finally, check that the source's first non-comment line begins with the
82 # special call 'remote()'
80 # special call 'remote()'
83 if require_remote:
81 if require_remote:
84 for nline,line in enumerate(src_lines):
82 for nline,line in enumerate(src_lines):
85 if line.isspace() or line.startswith('#'):
83 if line.isspace() or line.startswith('#'):
86 continue
84 continue
87 if line.startswith(remote_mark):
85 if line.startswith(remote_mark):
88 break
86 break
89 else:
87 else:
90 raise ValueError('%s call missing at the start of code' %
88 raise ValueError('%s call missing at the start of code' %
91 remote_mark)
89 remote_mark)
92 out_lines = src_lines[nline+1:]
90 out_lines = src_lines[nline+1:]
93 else:
91 else:
94 # If the user specified that the remote() call wasn't mandatory
92 # If the user specified that the remote() call wasn't mandatory
95 out_lines = src_lines
93 out_lines = src_lines
96
94
97 # src = ''.join(out_lines) # dbg
95 # src = ''.join(out_lines) # dbg
98 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
96 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
99 return ''.join(out_lines)
97 return ''.join(out_lines)
100
98
101 class RemoteContextBase(object):
99 class RemoteContextBase(object):
102 def __init__(self):
100 def __init__(self):
103 self.ip = ipapi.get()
101 self.ip = ipapi.get()
104
102
105 def _findsource_file(self,f):
103 def _findsource_file(self,f):
106 linecache.checkcache()
104 linecache.checkcache()
107 s = findsource(f.f_code)
105 s = findsource(f.f_code)
108 lnum = f.f_lineno
106 lnum = f.f_lineno
109 wsource = s[0][f.f_lineno:]
107 wsource = s[0][f.f_lineno:]
110 return strip_whitespace(wsource)
108 return strip_whitespace(wsource)
111
109
112 def _findsource_ipython(self,f):
110 def _findsource_ipython(self,f):
113 from IPython import ipapi
111 from IPython import ipapi
114 self.ip = ipapi.get()
112 self.ip = ipapi.get()
115 buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:]
113 buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:]
116 wsource = [l+'\n' for l in buf ]
114 wsource = [l+'\n' for l in buf ]
117
115
118 return strip_whitespace(wsource)
116 return strip_whitespace(wsource)
119
117
120 def findsource(self,frame):
118 def findsource(self,frame):
121 local_ns = frame.f_locals
119 local_ns = frame.f_locals
122 global_ns = frame.f_globals
120 global_ns = frame.f_globals
123 if frame.f_code.co_filename == '<ipython console>':
121 if frame.f_code.co_filename == '<ipython console>':
124 src = self._findsource_ipython(frame)
122 src = self._findsource_ipython(frame)
125 else:
123 else:
126 src = self._findsource_file(frame)
124 src = self._findsource_file(frame)
127 return src
125 return src
128
126
129 def __enter__(self):
127 def __enter__(self):
130 raise NotImplementedError
128 raise NotImplementedError
131
129
132 def __exit__ (self, etype, value, tb):
130 def __exit__ (self, etype, value, tb):
133 if issubclass(etype,error.StopLocalExecution):
131 if issubclass(etype,error.StopLocalExecution):
134 return True
132 return True
135
133
136 class RemoteMultiEngine(RemoteContextBase):
134 class RemoteMultiEngine(RemoteContextBase):
137 def __init__(self,mec):
135 def __init__(self,mec):
138 self.mec = mec
136 self.mec = mec
139 RemoteContextBase.__init__(self)
137 RemoteContextBase.__init__(self)
140
138
141 def __enter__(self):
139 def __enter__(self):
142 src = self.findsource(sys._getframe(1))
140 src = self.findsource(sys._getframe(1))
143 return self.mec.execute(src)
141 return self.mec.execute(src)
@@ -1,68 +1,70 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Test the output capture at the OS level, using file descriptors.
3 Test the output capture at the OS level, using file descriptors.
4 """
4 """
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15
15
16 # Stdlib imports
16 import os
17 import os
17 from cStringIO import StringIO
18 from cStringIO import StringIO
18
19
19 from IPython.testing import decorators as testdec
20 # Our own imports
21 from IPython.testing import decorators as dec
20
22
21 # FIXME
23 #-----------------------------------------------------------------------------
22 @testdec.skip("This doesn't work under Windows")
24 # Test functions
25
26 @dec.skip_win32
23 def test_redirector():
27 def test_redirector():
24 """ Checks that the redirector can be used to do synchronous capture.
28 """ Checks that the redirector can be used to do synchronous capture.
25 """
29 """
26 from IPython.kernel.core.fd_redirector import FDRedirector
30 from IPython.kernel.core.fd_redirector import FDRedirector
27 r = FDRedirector()
31 r = FDRedirector()
28 out = StringIO()
32 out = StringIO()
29 try:
33 try:
30 r.start()
34 r.start()
31 for i in range(10):
35 for i in range(10):
32 os.system('echo %ic' % i)
36 os.system('echo %ic' % i)
33 print >>out, r.getvalue(),
37 print >>out, r.getvalue(),
34 print >>out, i
38 print >>out, i
35 except:
39 except:
36 r.stop()
40 r.stop()
37 raise
41 raise
38 r.stop()
42 r.stop()
39 result1 = out.getvalue()
43 result1 = out.getvalue()
40 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
44 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
41 assert result1 == result2
45 assert result1 == result2
42
46
43 # FIXME
47
44 @testdec.skip("This doesn't work under Windows")
48 @dec.skip_win32
45 def test_redirector_output_trap():
49 def test_redirector_output_trap():
46 """ This test check not only that the redirector_output_trap does
50 """ This test check not only that the redirector_output_trap does
47 trap the output, but also that it does it in a gready way, that
51 trap the output, but also that it does it in a gready way, that
48 is by calling the callback ASAP.
52 is by calling the callback ASAP.
49 """
53 """
50 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
54 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
51 out = StringIO()
55 out = StringIO()
52 trap = RedirectorOutputTrap(out.write, out.write)
56 trap = RedirectorOutputTrap(out.write, out.write)
53 try:
57 try:
54 trap.set()
58 trap.set()
55 for i in range(10):
59 for i in range(10):
56 os.system('echo %ic' % i)
60 os.system('echo %ic' % i)
57 print "%ip" % i
61 print "%ip" % i
58 print >>out, i
62 print >>out, i
59 except:
63 except:
60 trap.unset()
64 trap.unset()
61 raise
65 raise
62 trap.unset()
66 trap.unset()
63 result1 = out.getvalue()
67 result1 = out.getvalue()
64 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
68 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
65 assert result1 == result2
69 assert result1 == result2
66
70
67
68
@@ -1,41 +1,43 b''
1 from __future__ import with_statement
1 #from __future__ import with_statement
2
3 # XXX This file is currently disabled to preserve 2.4 compatibility.
2
4
3 #def test_simple():
5 #def test_simple():
4 if 0:
6 if 0:
5
7
6 # XXX - for now, we need a running cluster to be started separately. The
8 # XXX - for now, we need a running cluster to be started separately. The
7 # daemon work is almost finished, and will make much of this unnecessary.
9 # daemon work is almost finished, and will make much of this unnecessary.
8 from IPython.kernel import client
10 from IPython.kernel import client
9 mec = client.MultiEngineClient(('127.0.0.1',10105))
11 mec = client.MultiEngineClient(('127.0.0.1',10105))
10
12
11 try:
13 try:
12 mec.get_ids()
14 mec.get_ids()
13 except ConnectionRefusedError:
15 except ConnectionRefusedError:
14 import os, time
16 import os, time
15 os.system('ipcluster -n 2 &')
17 os.system('ipcluster -n 2 &')
16 time.sleep(2)
18 time.sleep(2)
17 mec = client.MultiEngineClient(('127.0.0.1',10105))
19 mec = client.MultiEngineClient(('127.0.0.1',10105))
18
20
19 mec.block = False
21 mec.block = False
20
22
21 import itertools
23 import itertools
22 c = itertools.count()
24 c = itertools.count()
23
25
24 parallel = RemoteMultiEngine(mec)
26 parallel = RemoteMultiEngine(mec)
25
27
26 mec.pushAll()
28 mec.pushAll()
27
29
28 with parallel as pr:
30 ## with parallel as pr:
29 # A comment
31 ## # A comment
30 remote() # this means the code below only runs remotely
32 ## remote() # this means the code below only runs remotely
31 print 'Hello remote world'
33 ## print 'Hello remote world'
32 x = range(10)
34 ## x = range(10)
33 # Comments are OK
35 ## # Comments are OK
34 # Even misindented.
36 ## # Even misindented.
35 y = x+1
37 ## y = x+1
36
38
37
39
38 with pfor('i',sequence) as pr:
40 ## with pfor('i',sequence) as pr:
39 print x[i]
41 ## print x[i]
40
42
41 print pr.x + pr.y
43 print pr.x + pr.y
@@ -1,147 +1,160 b''
1 """Decorators for labeling test objects.
1 """Decorators for labeling test objects.
2
2
3 Decorators that merely return a modified version of the original
3 Decorators that merely return a modified version of the original
4 function object are straightforward. Decorators that return a new
4 function object are straightforward. Decorators that return a new
5 function object need to use
5 function object need to use
6 nose.tools.make_decorator(original_function)(decorator) in returning
6 nose.tools.make_decorator(original_function)(decorator) in returning
7 the decorator, in order to preserve metadata such as function name,
7 the decorator, in order to preserve metadata such as function name,
8 setup and teardown functions and so on - see nose.tools for more
8 setup and teardown functions and so on - see nose.tools for more
9 information.
9 information.
10
10
11 This module provides a set of useful decorators meant to be ready to use in
12 your own tests. See the bottom of the file for the ready-made ones, and if you
13 find yourself writing a new one that may be of generic use, add it here.
14
11 NOTE: This file contains IPython-specific decorators and imports the
15 NOTE: This file contains IPython-specific decorators and imports the
12 numpy.testing.decorators file, which we've copied verbatim. Any of our own
16 numpy.testing.decorators file, which we've copied verbatim. Any of our own
13 code will be added at the bottom if we end up extending this.
17 code will be added at the bottom if we end up extending this.
14 """
18 """
15
19
16 # Stdlib imports
20 # Stdlib imports
17 import inspect
21 import inspect
22 import sys
18
23
19 # Third-party imports
24 # Third-party imports
20
25
21 # This is Michele Simionato's decorator module, also kept verbatim.
26 # This is Michele Simionato's decorator module, also kept verbatim.
22 from decorator_msim import decorator, update_wrapper
27 from decorator_msim import decorator, update_wrapper
23
28
24 # Grab the numpy-specific decorators which we keep in a file that we
29 # Grab the numpy-specific decorators which we keep in a file that we
25 # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy
30 # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy
26 # of numpy.testing.decorators.
31 # of numpy.testing.decorators.
27 from decorators_numpy import *
32 from decorators_numpy import *
28
33
29 ##############################################################################
34 ##############################################################################
30 # Local code begins
35 # Local code begins
31
36
32 # Utility functions
37 # Utility functions
33
38
34 def apply_wrapper(wrapper,func):
39 def apply_wrapper(wrapper,func):
35 """Apply a wrapper to a function for decoration.
40 """Apply a wrapper to a function for decoration.
36
41
37 This mixes Michele Simionato's decorator tool with nose's make_decorator,
42 This mixes Michele Simionato's decorator tool with nose's make_decorator,
38 to apply a wrapper in a decorator so that all nose attributes, as well as
43 to apply a wrapper in a decorator so that all nose attributes, as well as
39 function signature and other properties, survive the decoration cleanly.
44 function signature and other properties, survive the decoration cleanly.
40 This will ensure that wrapped functions can still be well introspected via
45 This will ensure that wrapped functions can still be well introspected via
41 IPython, for example.
46 IPython, for example.
42 """
47 """
43 import nose.tools
48 import nose.tools
44
49
45 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
50 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
46
51
47
52
48 def make_label_dec(label,ds=None):
53 def make_label_dec(label,ds=None):
49 """Factory function to create a decorator that applies one or more labels.
54 """Factory function to create a decorator that applies one or more labels.
50
55
51 :Parameters:
56 :Parameters:
52 label : string or sequence
57 label : string or sequence
53 One or more labels that will be applied by the decorator to the functions
58 One or more labels that will be applied by the decorator to the functions
54 it decorates. Labels are attributes of the decorated function with their
59 it decorates. Labels are attributes of the decorated function with their
55 value set to True.
60 value set to True.
56
61
57 :Keywords:
62 :Keywords:
58 ds : string
63 ds : string
59 An optional docstring for the resulting decorator. If not given, a
64 An optional docstring for the resulting decorator. If not given, a
60 default docstring is auto-generated.
65 default docstring is auto-generated.
61
66
62 :Returns:
67 :Returns:
63 A decorator.
68 A decorator.
64
69
65 :Examples:
70 :Examples:
66
71
67 A simple labeling decorator:
72 A simple labeling decorator:
68 >>> slow = make_label_dec('slow')
73 >>> slow = make_label_dec('slow')
69 >>> print slow.__doc__
74 >>> print slow.__doc__
70 Labels a test as 'slow'.
75 Labels a test as 'slow'.
71
76
72 And one that uses multiple labels and a custom docstring:
77 And one that uses multiple labels and a custom docstring:
73 >>> rare = make_label_dec(['slow','hard'],
78 >>> rare = make_label_dec(['slow','hard'],
74 ... "Mix labels 'slow' and 'hard' for rare tests.")
79 ... "Mix labels 'slow' and 'hard' for rare tests.")
75 >>> print rare.__doc__
80 >>> print rare.__doc__
76 Mix labels 'slow' and 'hard' for rare tests.
81 Mix labels 'slow' and 'hard' for rare tests.
77
82
78 Now, let's test using this one:
83 Now, let's test using this one:
79 >>> @rare
84 >>> @rare
80 ... def f(): pass
85 ... def f(): pass
81 ...
86 ...
82 >>>
87 >>>
83 >>> f.slow
88 >>> f.slow
84 True
89 True
85 >>> f.hard
90 >>> f.hard
86 True
91 True
87 """
92 """
88
93
89 if isinstance(label,basestring):
94 if isinstance(label,basestring):
90 labels = [label]
95 labels = [label]
91 else:
96 else:
92 labels = label
97 labels = label
93
98
94 # Validate that the given label(s) are OK for use in setattr() by doing a
99 # Validate that the given label(s) are OK for use in setattr() by doing a
95 # dry run on a dummy function.
100 # dry run on a dummy function.
96 tmp = lambda : None
101 tmp = lambda : None
97 for label in labels:
102 for label in labels:
98 setattr(tmp,label,True)
103 setattr(tmp,label,True)
99
104
100 # This is the actual decorator we'll return
105 # This is the actual decorator we'll return
101 def decor(f):
106 def decor(f):
102 for label in labels:
107 for label in labels:
103 setattr(f,label,True)
108 setattr(f,label,True)
104 return f
109 return f
105
110
106 # Apply the user's docstring, or autogenerate a basic one
111 # Apply the user's docstring, or autogenerate a basic one
107 if ds is None:
112 if ds is None:
108 ds = "Labels a test as %r." % label
113 ds = "Labels a test as %r." % label
109 decor.__doc__ = ds
114 decor.__doc__ = ds
110
115
111 return decor
116 return decor
112
117
113 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
114 # Decorators for public use
119 # Decorators for public use
115
120
116 skip_doctest = make_label_dec('skip_doctest',
121 skip_doctest = make_label_dec('skip_doctest',
117 """Decorator - mark a function or method for skipping its doctest.
122 """Decorator - mark a function or method for skipping its doctest.
118
123
119 This decorator allows you to mark a function whose docstring you wish to
124 This decorator allows you to mark a function whose docstring you wish to
120 omit from testing, while preserving the docstring for introspection, help,
125 omit from testing, while preserving the docstring for introspection, help,
121 etc.""")
126 etc.""")
122
127
123 def skip(msg=''):
128 def skip(msg=''):
124 """Decorator - mark a test function for skipping from test suite.
129 """Decorator - mark a test function for skipping from test suite.
125
130
131 This function *is* already a decorator, it is not a factory like
132 make_label_dec or some of those in decorators_numpy.
133
126 :Parameters:
134 :Parameters:
127
135
128 func : function
136 func : function
129 Test function to be skipped
137 Test function to be skipped
130
138
131 msg : string
139 msg : string
132 Optional message to be added.
140 Optional message to be added.
133 """
141 """
134
142
135 import nose
143 import nose
136
144
137 def inner(func):
145 def inner(func):
138
146
139 def wrapper(*a,**k):
147 def wrapper(*a,**k):
140 if msg: out = '\n'+msg
148 if msg: out = '\n'+msg
141 else: out = ''
149 else: out = ''
142 raise nose.SkipTest("Skipping test for function: %s%s" %
150 raise nose.SkipTest("Skipping test for function: %s%s" %
143 (func.__name__,out))
151 (func.__name__,out))
144
152
145 return apply_wrapper(wrapper,func)
153 return apply_wrapper(wrapper,func)
146
154
147 return inner
155 return inner
156
157 # Decorators to skip certain tests on specific platforms.
158 skip_win32 = skipif(sys.platform=='win32',"This test does not run under Windows")
159 skip_linux = skipif(sys.platform=='linux2',"This test does not run under Linux")
160 skip_osx = skipif(sys.platform=='darwin',"This test does not run under OSX")
@@ -1,185 +1,51 b''
1 """Some simple tests for the plugin while running scripts.
2 """
1 # Module imports
3 # Module imports
2 # Std lib
4 # Std lib
3 import inspect
5 import inspect
4
6
5 # Third party
6
7 # Our own
7 # Our own
8 from IPython.testing import decorators as dec
8 from IPython.testing import decorators as dec
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Utilities
12
13 # Note: copied from OInspect, kept here so the testing stuff doesn't create
14 # circular dependencies and is easier to reuse.
15 def getargspec(obj):
16 """Get the names and default values of a function's arguments.
17
18 A tuple of four things is returned: (args, varargs, varkw, defaults).
19 'args' is a list of the argument names (it may contain nested lists).
20 'varargs' and 'varkw' are the names of the * and ** arguments or None.
21 'defaults' is an n-tuple of the default values of the last n arguments.
22
23 Modified version of inspect.getargspec from the Python Standard
24 Library."""
25
26 if inspect.isfunction(obj):
27 func_obj = obj
28 elif inspect.ismethod(obj):
29 func_obj = obj.im_func
30 else:
31 raise TypeError, 'arg is not a Python function'
32 args, varargs, varkw = inspect.getargs(func_obj.func_code)
33 return args, varargs, varkw, func_obj.func_defaults
34
35 #-----------------------------------------------------------------------------
36 # Testing functions
11 # Testing functions
37
12
38 def test_trivial():
13 def test_trivial():
39 """A trivial passing test."""
14 """A trivial passing test."""
40 pass
15 pass
41
16
42
43 @dec.skip
44 def test_deliberately_broken():
45 """A deliberately broken test - we want to skip this one."""
46 1/0
47
48 @dec.skip('foo')
49 def test_deliberately_broken2():
50 """Another deliberately broken test - we want to skip this one."""
51 1/0
52
53
54 # Verify that we can correctly skip the doctest for a function at will, but
55 # that the docstring itself is NOT destroyed by the decorator.
56 @dec.skip_doctest
57 def doctest_bad(x,y=1,**k):
58 """A function whose doctest we need to skip.
59
60 >>> 1+1
61 3
62 """
63 print 'x:',x
64 print 'y:',y
65 print 'k:',k
66
67
68 def call_doctest_bad():
69 """Check that we can still call the decorated functions.
70
71 >>> doctest_bad(3,y=4)
72 x: 3
73 y: 4
74 k: {}
75 """
76 pass
77
78
79 # Doctest skipping should work for class methods too
80 class foo(object):
81 """Foo
82
83 Example:
84
85 >>> 1+1
86 2
87 """
88
89 @dec.skip_doctest
90 def __init__(self,x):
91 """Make a foo.
92
93 Example:
94
95 >>> f = foo(3)
96 junk
97 """
98 print 'Making a foo.'
99 self.x = x
100
101 @dec.skip_doctest
102 def bar(self,y):
103 """Example:
104
105 >>> f = foo(3)
106 >>> f.bar(0)
107 boom!
108 >>> 1/0
109 bam!
110 """
111 return 1/y
112
113 def baz(self,y):
114 """Example:
115
116 >>> f = foo(3)
117 Making a foo.
118 >>> f.baz(3)
119 True
120 """
121 return self.x==y
122
123
124 def test_skip_dt_decorator():
125 """Doctest-skipping decorator should preserve the docstring.
126 """
127 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
128 check = """A function whose doctest we need to skip.
129
130 >>> 1+1
131 3
132 """
133 # Fetch the docstring from doctest_bad after decoration.
134 val = doctest_bad.__doc__
135
136 assert check==val,"doctest_bad docstrings don't match"
137
138
139 def test_skip_dt_decorator2():
140 """Doctest-skipping decorator should preserve function signature.
141 """
142 # Hardcoded correct answer
143 dtargs = (['x', 'y'], None, 'k', (1,))
144 # Introspect out the value
145 dtargsr = getargspec(doctest_bad)
146 assert dtargsr==dtargs, \
147 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
148
149
150 def doctest_run():
17 def doctest_run():
151 """Test running a trivial script.
18 """Test running a trivial script.
152
19
153 In [13]: run simplevars.py
20 In [13]: run simplevars.py
154 x is: 1
21 x is: 1
155 """
22 """
156
23
157 #@dec.skip_doctest
158 def doctest_runvars():
24 def doctest_runvars():
159 """Test that variables defined in scripts get loaded correcly via %run.
25 """Test that variables defined in scripts get loaded correcly via %run.
160
26
161 In [13]: run simplevars.py
27 In [13]: run simplevars.py
162 x is: 1
28 x is: 1
163
29
164 In [14]: x
30 In [14]: x
165 Out[14]: 1
31 Out[14]: 1
166 """
32 """
167
33
168 def doctest_ivars():
34 def doctest_ivars():
169 """Test that variables defined interactively are picked up.
35 """Test that variables defined interactively are picked up.
170 In [5]: zz=1
36 In [5]: zz=1
171
37
172 In [6]: zz
38 In [6]: zz
173 Out[6]: 1
39 Out[6]: 1
174 """
40 """
175
41
176 @dec.skip_doctest
42 @dec.skip_doctest
177 def doctest_refs():
43 def doctest_refs():
178 """DocTest reference holding issues when running scripts.
44 """DocTest reference holding issues when running scripts.
179
45
180 In [32]: run show_refs.py
46 In [32]: run show_refs.py
181 c referrers: [<type 'dict'>]
47 c referrers: [<type 'dict'>]
182
48
183 In [33]: map(type,gc.get_referrers(c))
49 In [33]: map(type,gc.get_referrers(c))
184 Out[33]: [<type 'dict'>]
50 Out[33]: [<type 'dict'>]
185 """
51 """
@@ -1,11 +1,11 b''
1 ===============
1 ==============
2 IPython1 README
2 IPython README
3 ===============
3 ==============
4
5 .. contents::
6
4
7 Overview
5 Overview
8 ========
6 ========
9
7
10 Welcome to IPython. New users should consult our documentation, which can be found
8 Welcome to IPython. Our documentation can be found in the docs/source
11 in the docs/source subdirectory.
9 subdirectory. We also have ``.html`` and ``.pdf`` versions of this
10 documentation available on the IPython `website <http://ipython.scipy.org>`_.
11
@@ -1,120 +1,123 b''
1 """Example showing how to merge multiple remote data streams.
1 """Example showing how to merge multiple remote data streams.
2 """
2 """
3 # Slightly modified version of:
3 # Slightly modified version of:
4 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511509
4 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511509
5
5
6 import heapq
6 import heapq
7 from IPython.kernel.error import CompositeError
7 from IPython.kernel.error import CompositeError
8
8
9 def mergesort(list_of_lists, key=None):
9 def mergesort(list_of_lists, key=None):
10 """ Perform an N-way merge operation on sorted lists.
10 """ Perform an N-way merge operation on sorted lists.
11
11
12 @param list_of_lists: (really iterable of iterable) of sorted elements
12 @param list_of_lists: (really iterable of iterable) of sorted elements
13 (either by naturally or by C{key})
13 (either by naturally or by C{key})
14 @param key: specify sort key function (like C{sort()}, C{sorted()})
14 @param key: specify sort key function (like C{sort()}, C{sorted()})
15
15
16 Yields tuples of the form C{(item, iterator)}, where the iterator is the
16 Yields tuples of the form C{(item, iterator)}, where the iterator is the
17 built-in list iterator or something you pass in, if you pre-generate the
17 built-in list iterator or something you pass in, if you pre-generate the
18 iterators.
18 iterators.
19
19
20 This is a stable merge; complexity O(N lg N)
20 This is a stable merge; complexity O(N lg N)
21
21
22 Examples::
22 Examples::
23
23
24 >>> print list(mergesort([[1,2,3,4],
24 >>> print list(mergesort([[1,2,3,4],
25 ... [2,3.25,3.75,4.5,6,7],
25 ... [2,3.25,3.75,4.5,6,7],
26 ... [2.625,3.625,6.625,9]]))
26 ... [2.625,3.625,6.625,9]]))
27 [1, 2, 2, 2.625, 3, 3.25, 3.625, 3.75, 4, 4.5, 6, 6.625, 7, 9]
27 [1, 2, 2, 2.625, 3, 3.25, 3.625, 3.75, 4, 4.5, 6, 6.625, 7, 9]
28
28
29 # note stability
29 # note stability
30 >>> print list(mergesort([[1,2,3,4],
30 >>> print list(mergesort([[1,2,3,4],
31 ... [2,3.25,3.75,4.5,6,7],
31 ... [2,3.25,3.75,4.5,6,7],
32 ... [2.625,3.625,6.625,9]],
32 ... [2.625,3.625,6.625,9]],
33 ... key=int))
33 ... key=int))
34 [1, 2, 2, 2.625, 3, 3.25, 3.75, 3.625, 4, 4.5, 6, 6.625, 7, 9]
34 [1, 2, 2, 2.625, 3, 3.25, 3.75, 3.625, 4, 4.5, 6, 6.625, 7, 9]
35
35
36
36
37 >>> print list(mergesort([[4, 3, 2, 1],
37 >>> print list(mergesort([[4, 3, 2, 1],
38 ... [7, 6, 4.5, 3.75, 3.25, 2],
38 ... [7, 6, 4.5, 3.75, 3.25, 2],
39 ... [9, 6.625, 3.625, 2.625]],
39 ... [9, 6.625, 3.625, 2.625]],
40 ... key=lambda x: -x))
40 ... key=lambda x: -x))
41 [9, 7, 6.625, 6, 4.5, 4, 3.75, 3.625, 3.25, 3, 2.625, 2, 2, 1]
41 [9, 7, 6.625, 6, 4.5, 4, 3.75, 3.625, 3.25, 3, 2.625, 2, 2, 1]
42 """
42 """
43
43
44 heap = []
44 heap = []
45 for i, itr in enumerate(iter(pl) for pl in list_of_lists):
45 for i, itr in enumerate(iter(pl) for pl in list_of_lists):
46 try:
46 try:
47 item = itr.next()
47 item = itr.next()
48 toadd = (key(item), i, item, itr) if key else (item, i, itr)
48 if key:
49 toadd = (key(item), i, item, itr)
50 else:
51 toadd = (item, i, itr)
49 heap.append(toadd)
52 heap.append(toadd)
50 except StopIteration:
53 except StopIteration:
51 pass
54 pass
52 heapq.heapify(heap)
55 heapq.heapify(heap)
53
56
54 if key:
57 if key:
55 while heap:
58 while heap:
56 _, idx, item, itr = heap[0]
59 _, idx, item, itr = heap[0]
57 yield item
60 yield item
58 try:
61 try:
59 item = itr.next()
62 item = itr.next()
60 heapq.heapreplace(heap, (key(item), idx, item, itr) )
63 heapq.heapreplace(heap, (key(item), idx, item, itr) )
61 except StopIteration:
64 except StopIteration:
62 heapq.heappop(heap)
65 heapq.heappop(heap)
63
66
64 else:
67 else:
65 while heap:
68 while heap:
66 item, idx, itr = heap[0]
69 item, idx, itr = heap[0]
67 yield item
70 yield item
68 try:
71 try:
69 heapq.heapreplace(heap, (itr.next(), idx, itr))
72 heapq.heapreplace(heap, (itr.next(), idx, itr))
70 except StopIteration:
73 except StopIteration:
71 heapq.heappop(heap)
74 heapq.heappop(heap)
72
75
73
76
74 def remote_iterator(rc,engine,name):
77 def remote_iterator(rc,engine,name):
75 """Return an iterator on an object living on a remote engine.
78 """Return an iterator on an object living on a remote engine.
76 """
79 """
77 # Check that the object exists on the engine and pin a reference to it
80 # Check that the object exists on the engine and pin a reference to it
78 iter_name = '_%s_rmt_iter_' % name
81 iter_name = '_%s_rmt_iter_' % name
79 rc.execute('%s = iter(%s)' % (iter_name,name), targets=engine)
82 rc.execute('%s = iter(%s)' % (iter_name,name), targets=engine)
80 tpl = '_tmp = %s.next()' % iter_name
83 tpl = '_tmp = %s.next()' % iter_name
81 while True:
84 while True:
82 try:
85 try:
83 rc.execute(tpl, targets=engine)
86 rc.execute(tpl, targets=engine)
84 result = rc.pull('_tmp', targets=engine)[0]
87 result = rc.pull('_tmp', targets=engine)[0]
85 # This causes the StopIteration exception to be raised.
88 # This causes the StopIteration exception to be raised.
86 except CompositeError, e:
89 except CompositeError, e:
87 e.raise_exception()
90 e.raise_exception()
88 else:
91 else:
89 yield result
92 yield result
90
93
91 # Main, interactive testing
94 # Main, interactive testing
92 if __name__ == '__main__':
95 if __name__ == '__main__':
93
96
94 from IPython.kernel import client
97 from IPython.kernel import client
95 ipc = client.MultiEngineClient()
98 ipc = client.MultiEngineClient()
96 print 'Engine IDs:',ipc.get_ids()
99 print 'Engine IDs:',ipc.get_ids()
97
100
98 # Make a set of 'sorted datasets'
101 # Make a set of 'sorted datasets'
99 a0 = range(5,20)
102 a0 = range(5,20)
100 a1 = range(10)
103 a1 = range(10)
101 a2 = range(15,25)
104 a2 = range(15,25)
102
105
103 # Now, imagine these had been created in the remote engines by some long
106 # Now, imagine these had been created in the remote engines by some long
104 # computation. In this simple example, we just send them over into the
107 # computation. In this simple example, we just send them over into the
105 # remote engines. They will all be called 'a' in each engine.
108 # remote engines. They will all be called 'a' in each engine.
106 ipc.push(dict(a=a0), targets=0)
109 ipc.push(dict(a=a0), targets=0)
107 ipc.push(dict(a=a1), targets=1)
110 ipc.push(dict(a=a1), targets=1)
108 ipc.push(dict(a=a2), targets=2)
111 ipc.push(dict(a=a2), targets=2)
109
112
110 # And we now make a local object which represents the remote iterator
113 # And we now make a local object which represents the remote iterator
111 aa0 = remote_iterator(ipc,0,'a')
114 aa0 = remote_iterator(ipc,0,'a')
112 aa1 = remote_iterator(ipc,1,'a')
115 aa1 = remote_iterator(ipc,1,'a')
113 aa2 = remote_iterator(ipc,2,'a')
116 aa2 = remote_iterator(ipc,2,'a')
114
117
115 # Let's merge them, both locally and remotely:
118 # Let's merge them, both locally and remotely:
116 print 'Merge the local datasets:'
119 print 'Merge the local datasets:'
117 print list(mergesort([a0,a1,a2]))
120 print list(mergesort([a0,a1,a2]))
118
121
119 print 'Locally merge the remote sets:'
122 print 'Locally merge the remote sets:'
120 print list(mergesort([aa0,aa1,aa2]))
123 print list(mergesort([aa0,aa1,aa2]))
@@ -1,233 +1,233 b''
1 .. _overview:
1 .. _overview:
2
2
3 ============
3 ============
4 Introduction
4 Introduction
5 ============
5 ============
6
6
7 Overview
7 Overview
8 ========
8 ========
9
9
10 One of Python's most useful features is its interactive interpreter.
10 One of Python's most useful features is its interactive interpreter.
11 This system allows very fast testing of ideas without the overhead of
11 This system allows very fast testing of ideas without the overhead of
12 creating test files as is typical in most programming languages.
12 creating test files as is typical in most programming languages.
13 However, the interpreter supplied with the standard Python distribution
13 However, the interpreter supplied with the standard Python distribution
14 is somewhat limited for extended interactive use.
14 is somewhat limited for extended interactive use.
15
15
16 The goal of IPython is to create a comprehensive environment for
16 The goal of IPython is to create a comprehensive environment for
17 interactive and exploratory computing. To support, this goal, IPython
17 interactive and exploratory computing. To support this goal, IPython
18 has two main components:
18 has two main components:
19
19
20 * An enhanced interactive Python shell.
20 * An enhanced interactive Python shell.
21 * An architecture for interactive parallel computing.
21 * An architecture for interactive parallel computing.
22
22
23 All of IPython is open source (released under the revised BSD license).
23 All of IPython is open source (released under the revised BSD license).
24
24
25 Enhanced interactive Python shell
25 Enhanced interactive Python shell
26 =================================
26 =================================
27
27
28 IPython's interactive shell (:command:`ipython`), has the following goals,
28 IPython's interactive shell (:command:`ipython`), has the following goals,
29 amongst others:
29 amongst others:
30
30
31 1. Provide an interactive shell superior to Python's default. IPython
31 1. Provide an interactive shell superior to Python's default. IPython
32 has many features for object introspection, system shell access,
32 has many features for object introspection, system shell access,
33 and its own special command system for adding functionality when
33 and its own special command system for adding functionality when
34 working interactively. It tries to be a very efficient environment
34 working interactively. It tries to be a very efficient environment
35 both for Python code development and for exploration of problems
35 both for Python code development and for exploration of problems
36 using Python objects (in situations like data analysis).
36 using Python objects (in situations like data analysis).
37
37
38 2. Serve as an embeddable, ready to use interpreter for your own
38 2. Serve as an embeddable, ready to use interpreter for your own
39 programs. IPython can be started with a single call from inside
39 programs. IPython can be started with a single call from inside
40 another program, providing access to the current namespace. This
40 another program, providing access to the current namespace. This
41 can be very useful both for debugging purposes and for situations
41 can be very useful both for debugging purposes and for situations
42 where a blend of batch-processing and interactive exploration are
42 where a blend of batch-processing and interactive exploration are
43 needed. New in the 0.9 version of IPython is a reusable wxPython
43 needed. New in the 0.9 version of IPython is a reusable wxPython
44 based IPython widget.
44 based IPython widget.
45
45
46 3. Offer a flexible framework which can be used as the base
46 3. Offer a flexible framework which can be used as the base
47 environment for other systems with Python as the underlying
47 environment for other systems with Python as the underlying
48 language. Specifically scientific environments like Mathematica,
48 language. Specifically scientific environments like Mathematica,
49 IDL and Matlab inspired its design, but similar ideas can be
49 IDL and Matlab inspired its design, but similar ideas can be
50 useful in many fields.
50 useful in many fields.
51
51
52 4. Allow interactive testing of threaded graphical toolkits. IPython
52 4. Allow interactive testing of threaded graphical toolkits. IPython
53 has support for interactive, non-blocking control of GTK, Qt and
53 has support for interactive, non-blocking control of GTK, Qt and
54 WX applications via special threading flags. The normal Python
54 WX applications via special threading flags. The normal Python
55 shell can only do this for Tkinter applications.
55 shell can only do this for Tkinter applications.
56
56
57 Main features of the interactive shell
57 Main features of the interactive shell
58 --------------------------------------
58 --------------------------------------
59
59
60 * Dynamic object introspection. One can access docstrings, function
60 * Dynamic object introspection. One can access docstrings, function
61 definition prototypes, source code, source files and other details
61 definition prototypes, source code, source files and other details
62 of any object accessible to the interpreter with a single
62 of any object accessible to the interpreter with a single
63 keystroke (:samp:`?`, and using :samp:`??` provides additional detail).
63 keystroke (:samp:`?`, and using :samp:`??` provides additional detail).
64
64
65 * Searching through modules and namespaces with :samp:`*` wildcards, both
65 * Searching through modules and namespaces with :samp:`*` wildcards, both
66 when using the :samp:`?` system and via the :samp:`%psearch` command.
66 when using the :samp:`?` system and via the :samp:`%psearch` command.
67
67
68 * Completion in the local namespace, by typing :kbd:`TAB` at the prompt.
68 * Completion in the local namespace, by typing :kbd:`TAB` at the prompt.
69 This works for keywords, modules, methods, variables and files in the
69 This works for keywords, modules, methods, variables and files in the
70 current directory. This is supported via the readline library, and
70 current directory. This is supported via the readline library, and
71 full access to configuring readline's behavior is provided.
71 full access to configuring readline's behavior is provided.
72 Custom completers can be implemented easily for different purposes
72 Custom completers can be implemented easily for different purposes
73 (system commands, magic arguments etc.)
73 (system commands, magic arguments etc.)
74
74
75 * Numbered input/output prompts with command history (persistent
75 * Numbered input/output prompts with command history (persistent
76 across sessions and tied to each profile), full searching in this
76 across sessions and tied to each profile), full searching in this
77 history and caching of all input and output.
77 history and caching of all input and output.
78
78
79 * User-extensible 'magic' commands. A set of commands prefixed with
79 * User-extensible 'magic' commands. A set of commands prefixed with
80 :samp:`%` is available for controlling IPython itself and provides
80 :samp:`%` is available for controlling IPython itself and provides
81 directory control, namespace information and many aliases to
81 directory control, namespace information and many aliases to
82 common system shell commands.
82 common system shell commands.
83
83
84 * Alias facility for defining your own system aliases.
84 * Alias facility for defining your own system aliases.
85
85
86 * Complete system shell access. Lines starting with :samp:`!` are passed
86 * Complete system shell access. Lines starting with :samp:`!` are passed
87 directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd`
87 directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd`
88 captures shell output into python variables for further use.
88 captures shell output into python variables for further use.
89
89
90 * Background execution of Python commands in a separate thread.
90 * Background execution of Python commands in a separate thread.
91 IPython has an internal job manager called jobs, and a
91 IPython has an internal job manager called jobs, and a
92 convenience backgrounding magic function called :samp:`%bg`.
92 convenience backgrounding magic function called :samp:`%bg`.
93
93
94 * The ability to expand python variables when calling the system
94 * The ability to expand python variables when calling the system
95 shell. In a shell command, any python variable prefixed with :samp:`$` is
95 shell. In a shell command, any python variable prefixed with :samp:`$` is
96 expanded. A double :samp:`$$` allows passing a literal :samp:`$` to the shell (for
96 expanded. A double :samp:`$$` allows passing a literal :samp:`$` to the shell (for
97 access to shell and environment variables like :envvar:`PATH`).
97 access to shell and environment variables like :envvar:`PATH`).
98
98
99 * Filesystem navigation, via a magic :samp:`%cd` command, along with a
99 * Filesystem navigation, via a magic :samp:`%cd` command, along with a
100 persistent bookmark system (using :samp:`%bookmark`) for fast access to
100 persistent bookmark system (using :samp:`%bookmark`) for fast access to
101 frequently visited directories.
101 frequently visited directories.
102
102
103 * A lightweight persistence framework via the :samp:`%store` command, which
103 * A lightweight persistence framework via the :samp:`%store` command, which
104 allows you to save arbitrary Python variables. These get restored
104 allows you to save arbitrary Python variables. These get restored
105 automatically when your session restarts.
105 automatically when your session restarts.
106
106
107 * Automatic indentation (optional) of code as you type (through the
107 * Automatic indentation (optional) of code as you type (through the
108 readline library).
108 readline library).
109
109
110 * Macro system for quickly re-executing multiple lines of previous
110 * Macro system for quickly re-executing multiple lines of previous
111 input with a single name. Macros can be stored persistently via
111 input with a single name. Macros can be stored persistently via
112 :samp:`%store` and edited via :samp:`%edit`.
112 :samp:`%store` and edited via :samp:`%edit`.
113
113
114 * Session logging (you can then later use these logs as code in your
114 * Session logging (you can then later use these logs as code in your
115 programs). Logs can optionally timestamp all input, and also store
115 programs). Logs can optionally timestamp all input, and also store
116 session output (marked as comments, so the log remains valid
116 session output (marked as comments, so the log remains valid
117 Python source code).
117 Python source code).
118
118
119 * Session restoring: logs can be replayed to restore a previous
119 * Session restoring: logs can be replayed to restore a previous
120 session to the state where you left it.
120 session to the state where you left it.
121
121
122 * Verbose and colored exception traceback printouts. Easier to parse
122 * Verbose and colored exception traceback printouts. Easier to parse
123 visually, and in verbose mode they produce a lot of useful
123 visually, and in verbose mode they produce a lot of useful
124 debugging information (basically a terminal version of the cgitb
124 debugging information (basically a terminal version of the cgitb
125 module).
125 module).
126
126
127 * Auto-parentheses: callable objects can be executed without
127 * Auto-parentheses: callable objects can be executed without
128 parentheses: :samp:`sin 3` is automatically converted to :samp:`sin(3)`.
128 parentheses: :samp:`sin 3` is automatically converted to :samp:`sin(3)`.
129
129
130 * Auto-quoting: using :samp:`,`, or :samp:`;` as the first character forces
130 * Auto-quoting: using :samp:`,`, or :samp:`;` as the first character forces
131 auto-quoting of the rest of the line: :samp:`,my_function a b` becomes
131 auto-quoting of the rest of the line: :samp:`,my_function a b` becomes
132 automatically :samp:`my_function("a","b")`, while :samp:`;my_function a b`
132 automatically :samp:`my_function("a","b")`, while :samp:`;my_function a b`
133 becomes :samp:`my_function("a b")`.
133 becomes :samp:`my_function("a b")`.
134
134
135 * Extensible input syntax. You can define filters that pre-process
135 * Extensible input syntax. You can define filters that pre-process
136 user input to simplify input in special situations. This allows
136 user input to simplify input in special situations. This allows
137 for example pasting multi-line code fragments which start with
137 for example pasting multi-line code fragments which start with
138 :samp:`>>>` or :samp:`...` such as those from other python sessions or the
138 :samp:`>>>` or :samp:`...` such as those from other python sessions or the
139 standard Python documentation.
139 standard Python documentation.
140
140
141 * Flexible configuration system. It uses a configuration file which
141 * Flexible configuration system. It uses a configuration file which
142 allows permanent setting of all command-line options, module
142 allows permanent setting of all command-line options, module
143 loading, code and file execution. The system allows recursive file
143 loading, code and file execution. The system allows recursive file
144 inclusion, so you can have a base file with defaults and layers
144 inclusion, so you can have a base file with defaults and layers
145 which load other customizations for particular projects.
145 which load other customizations for particular projects.
146
146
147 * Embeddable. You can call IPython as a python shell inside your own
147 * Embeddable. You can call IPython as a python shell inside your own
148 python programs. This can be used both for debugging code or for
148 python programs. This can be used both for debugging code or for
149 providing interactive abilities to your programs with knowledge
149 providing interactive abilities to your programs with knowledge
150 about the local namespaces (very useful in debugging and data
150 about the local namespaces (very useful in debugging and data
151 analysis situations).
151 analysis situations).
152
152
153 * Easy debugger access. You can set IPython to call up an enhanced
153 * Easy debugger access. You can set IPython to call up an enhanced
154 version of the Python debugger (pdb) every time there is an
154 version of the Python debugger (pdb) every time there is an
155 uncaught exception. This drops you inside the code which triggered
155 uncaught exception. This drops you inside the code which triggered
156 the exception with all the data live and it is possible to
156 the exception with all the data live and it is possible to
157 navigate the stack to rapidly isolate the source of a bug. The
157 navigate the stack to rapidly isolate the source of a bug. The
158 :samp:`%run` magic command (with the :samp:`-d` option) can run any script under
158 :samp:`%run` magic command (with the :samp:`-d` option) can run any script under
159 pdb's control, automatically setting initial breakpoints for you.
159 pdb's control, automatically setting initial breakpoints for you.
160 This version of pdb has IPython-specific improvements, including
160 This version of pdb has IPython-specific improvements, including
161 tab-completion and traceback coloring support. For even easier
161 tab-completion and traceback coloring support. For even easier
162 debugger access, try :samp:`%debug` after seeing an exception. winpdb is
162 debugger access, try :samp:`%debug` after seeing an exception. winpdb is
163 also supported, see ipy_winpdb extension.
163 also supported, see ipy_winpdb extension.
164
164
165 * Profiler support. You can run single statements (similar to
165 * Profiler support. You can run single statements (similar to
166 :samp:`profile.run()`) or complete programs under the profiler's control.
166 :samp:`profile.run()`) or complete programs under the profiler's control.
167 While this is possible with standard cProfile or profile modules,
167 While this is possible with standard cProfile or profile modules,
168 IPython wraps this functionality with magic commands (see :samp:`%prun`
168 IPython wraps this functionality with magic commands (see :samp:`%prun`
169 and :samp:`%run -p`) convenient for rapid interactive work.
169 and :samp:`%run -p`) convenient for rapid interactive work.
170
170
171 * Doctest support. The special :samp:`%doctest_mode` command toggles a mode
171 * Doctest support. The special :samp:`%doctest_mode` command toggles a mode
172 that allows you to paste existing doctests (with leading :samp:`>>>`
172 that allows you to paste existing doctests (with leading :samp:`>>>`
173 prompts and whitespace) and uses doctest-compatible prompts and
173 prompts and whitespace) and uses doctest-compatible prompts and
174 output, so you can use IPython sessions as doctest code.
174 output, so you can use IPython sessions as doctest code.
175
175
176 Interactive parallel computing
176 Interactive parallel computing
177 ==============================
177 ==============================
178
178
179 Increasingly, parallel computer hardware, such as multicore CPUs, clusters and supercomputers, is becoming ubiquitous. Over the last 3 years, we have developed an
179 Increasingly, parallel computer hardware, such as multicore CPUs, clusters and supercomputers, is becoming ubiquitous. Over the last 3 years, we have developed an
180 architecture within IPython that allows such hardware to be used quickly and easily
180 architecture within IPython that allows such hardware to be used quickly and easily
181 from Python. Moreover, this architecture is designed to support interactive and
181 from Python. Moreover, this architecture is designed to support interactive and
182 collaborative parallel computing.
182 collaborative parallel computing.
183
183
184 The main features of this system are:
184 The main features of this system are:
185
185
186 * Quickly parallelize Python code from an interactive Python/IPython session.
186 * Quickly parallelize Python code from an interactive Python/IPython session.
187
187
188 * A flexible and dynamic process model that be deployed on anything from
188 * A flexible and dynamic process model that be deployed on anything from
189 multicore workstations to supercomputers.
189 multicore workstations to supercomputers.
190
190
191 * An architecture that supports many different styles of parallelism, from
191 * An architecture that supports many different styles of parallelism, from
192 message passing to task farming. And all of these styles can be handled
192 message passing to task farming. And all of these styles can be handled
193 interactively.
193 interactively.
194
194
195 * Both blocking and fully asynchronous interfaces.
195 * Both blocking and fully asynchronous interfaces.
196
196
197 * High level APIs that enable many things to be parallelized in a few lines
197 * High level APIs that enable many things to be parallelized in a few lines
198 of code.
198 of code.
199
199
200 * Write parallel code that will run unchanged on everything from multicore
200 * Write parallel code that will run unchanged on everything from multicore
201 workstations to supercomputers.
201 workstations to supercomputers.
202
202
203 * Full integration with Message Passing libraries (MPI).
203 * Full integration with Message Passing libraries (MPI).
204
204
205 * Capabilities based security model with full encryption of network connections.
205 * Capabilities based security model with full encryption of network connections.
206
206
207 * Share live parallel jobs with other users securely. We call this collaborative
207 * Share live parallel jobs with other users securely. We call this collaborative
208 parallel computing.
208 parallel computing.
209
209
210 * Dynamically load balanced task farming system.
210 * Dynamically load balanced task farming system.
211
211
212 * Robust error handling. Python exceptions raised in parallel execution are
212 * Robust error handling. Python exceptions raised in parallel execution are
213 gathered and presented to the top-level code.
213 gathered and presented to the top-level code.
214
214
215 For more information, see our :ref:`overview <parallel_index>` of using IPython for
215 For more information, see our :ref:`overview <parallel_index>` of using IPython for
216 parallel computing.
216 parallel computing.
217
217
218 Portability and Python requirements
218 Portability and Python requirements
219 -----------------------------------
219 -----------------------------------
220
220
221 As of the 0.9 release, IPython requires Python 2.4 or greater. We have
221 As of the 0.9 release, IPython requires Python 2.4 or greater. We have
222 not begun to test IPython on Python 2.6 or 3.0, but we expect it will
222 not begun to test IPython on Python 2.6 or 3.0, but we expect it will
223 work with some minor changes.
223 work with some minor changes.
224
224
225 IPython is known to work on the following operating systems:
225 IPython is known to work on the following operating systems:
226
226
227 * Linux
227 * Linux
228 * AIX
228 * AIX
229 * Most other Unix-like OSs (Solaris, BSD, etc.)
229 * Most other Unix-like OSs (Solaris, BSD, etc.)
230 * Mac OS X
230 * Mac OS X
231 * Windows (CygWin, XP, Vista, etc.)
231 * Windows (CygWin, XP, Vista, etc.)
232
232
233 See :ref:`here <install_index>` for instructions on how to install IPython. No newline at end of file
233 See :ref:`here <install_index>` for instructions on how to install IPython.
1 NO CONTENT: file renamed from IPython/config/config.py to sandbox/config.py
NO CONTENT: file renamed from IPython/config/config.py to sandbox/config.py
1 NO CONTENT: file renamed from IPython/config/tests/sample_config.py to sandbox/sample_config.py
NO CONTENT: file renamed from IPython/config/tests/sample_config.py to sandbox/sample_config.py
1 NO CONTENT: file renamed from IPython/config/tests/test_config.py to sandbox/test_config.py
NO CONTENT: file renamed from IPython/config/tests/test_config.py to sandbox/test_config.py
1 NO CONTENT: file renamed from IPython/config/traitlets.py to sandbox/traitlets.py
NO CONTENT: file renamed from IPython/config/traitlets.py to sandbox/traitlets.py
@@ -1,31 +1,31 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # release test
3 # release test
4
4
5 ipdir=$PWD/..
5 ipdir=$PWD/..
6
6
7 cd $ipdir
7 cd $ipdir
8
8
9 # Clean up build/dist directories
9 # Clean up build/dist directories
10 rm -rf $ipdir/build/*
10 rm -rf $ipdir/build/*
11 rm -rf $ipdir/dist/*
11 rm -rf $ipdir/dist/*
12
12
13 # build source distros
13 # build source distros
14 cd $ipdir
14 cd $ipdir
15 ./setup.py sdist --formats=gztar
15 ./setup.py sdist --formats=gztar
16
16
17 # Build rpms
17 # Build rpms
18 #python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4
18 python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4
19 #python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5
19 python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5
20
20
21 # Build eggs
21 # Build eggs
22 python2.4 ./setup_bdist_egg.py
22 python2.4 ./setup_bdist_egg.py
23 python2.5 ./setup_bdist_egg.py
23 python2.5 ./setup_bdist_egg.py
24
24
25 # Call the windows build separately, so that the extra Windows scripts don't
25 # Call the windows build separately, so that the extra Windows scripts don't
26 # get pulled into Unix builds (setup.py has code which checks for
26 # get pulled into Unix builds (setup.py has code which checks for
27 # bdist_wininst)
27 # bdist_wininst)
28 ./setup.py bdist_wininst --install-script=ipython_win_post_install.py
28 ./setup.py bdist_wininst --install-script=ipython_win_post_install.py
29
29
30 # Change name so retarded Vista runs the installer correctly
30 # Change name so retarded Vista runs the installer correctly
31 rename 's/win32/win32-setup/' $ipdir/dist/*.exe
31 rename 's/win32/win32-setup/' $ipdir/dist/*.exe
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